javamultithreadingparallel-processingjava-threadsthread-local

Java ThreadLocal returns null value


I have some complex calculation that needs to be done parallel in my code and I needed some mechanism to properly log stuff to my own data structure. I didn't want to pass this data structure down to every class my calculations are using (there are a lot), nor didn't want to create one in everywhere and return it, so I came up with an idea to store it in a static ThreadLocal that can be accessed by my code thats running inside a job. It looks something like this:

public class MyLoggerWrapper
{
    private static ThreadLocal<MyLogger> threadLocalMyLogger;
    
    public static MyLogger myLogger() 
    {
        return threadLocalMyLogger.get();
    }
    
    static void init(final SomeParameter parameter) 
    {       
        MyLoggerWrapper.threadLocalMyLogger = new ThreadLocal<>();
        MyLoggerWrapper.threadLocalMyLogger.set(new MyLogger(parameter));
    }
    
    static void remove() 
    {
        threadLocalMyLogger.remove();
    }
}

My callable class looks something like this:

public class MyJob implements Callable<Something>
{
    @Override
    public Something call() throws Exception
    {
        try 
        {
            MyLoggerWrapper.init(someParameter);
            
            return ...doSomeWork();
        } 
        catch (final Exception e) 
        {
            ....doSomeOtherStuff();
        }
        finally 
        {
            MyLoggerWrapper.remove();
        }
    }
}

In places where i need to log something i would call the static logger like so:

MyLoggerWrapper.myLogger().logStuff(); 

When I was testing it with a few threads it was working fine, but when i hit around 200 - 250 parallel calculations it started to throw nullpointers because

MyLoggerWrapper.myLogger();

returned null. It is never supposed to be null since the first thing my job does is to instantiate one.

(I dint use ThreadLocal.withInitial() on purpose because it seem to me that only one supplier is present for every thread and it can be overridden, and I need different input parameters for different jobs.)

Any idea what might cause this? Am I missing something with how ThreadLocal works, or am i not aware of some kind of limitation?


Solution

  • Your code only requires one instance of ThreadLocal, you have many threads wiping the last initialised value of threadLocalMyLogger inside init().

    Try with one final declaration and remove the per Thread assignment:

    private static final ThreadLocal<MyLogger> threadLocalMyLogger = new ThreadLocal<>(); 
    
    static void init(final SomeParameter parameter) 
    {       
        // MyLoggerWrapper.threadLocalMyLogger = new ThreadLocal<>();
        MyLoggerWrapper.threadLocalMyLogger.set(new MyLogger(parameter));
    }