//
you're reading...
Java Concurrency

A date with ThreadLocal

Very recently I was stuck amidst debugging a strange concurrency related problem. The code in question was making use of ThreadLocal, but it wasn’t behaving the way in which I was expecting it to. Without further adieu, lets look at a similar example which can simulate the problem that I had.

public class ThreadLocalProblem {
    private static ThreadLocal<String> names;

    public static void main(String[] args) throws InterruptedException {
        greet("KungFu Panda");
        Runnable runner = new Runnable() {
            @Override
            public void run() {
                String msg = String.format("The [%d] clan welcomes you [%s] oh great Dragon Warrior!",
                    Thread.currentThread().getId(),names.get());
                System.err.println(msg);
            }
        };
        Thread thread = new Thread(runner);
        thread.start();
        thread.join();

    }

    private static void greet(String name) {
        final String userName = name;
        names = new ThreadLocal<String>() {
            @Override
            protected String initialValue() {
                return userName;
            }
        };
        String msg = String.format("The [%d] clan welcomes you [%s] oh great Dragon Warrior!",
            Thread.currentThread().getId(),names.get());
        System.err.println(msg);
    }
}

As you can see, the above is a very simple and straight forward example, which makes use of ThreadLocal.
The main method, basically attempts to spin off two threads [ One thread is the main Thread and the other one is basically a child thread that the main method spawns] and tries to access their ThreadLocal values.

The output looks like below:

The [1] clan welcomes you [KungFu Panda] oh great Dragon Warrior!
The [9] clan welcomes you [KungFu Panda] oh great Dragon Warrior!

Process finished with exit code 0

The above output raises some interesting questions :

  1. How did the child thread that was spun off via runner get its initial value as “KungFu Panda” ?
  2. How did Java manage to pass on the value that we thought we set as the initial thread context for one thread, to the other thread even though we were working with ThreadLocals.

Well, it took me a couple of hours before I figured out what was a very baffling learning.

Lets execute this code line by line manually.

  • When greet(“KungFu Panda”); gets called, the call hierarchy goes via main() -> greet().
  • The value of “KungFu Panda” gets set to the variable “name” which then gets used as the initial value when a ThreadLocal is created. So Thread Id “1” would now have a value of “KungFu Panda”. Till here, we are all clear and all is working well.
  • Now the next line basically creates an thread which invokes the ThreadLocal’s get() method. Here I was expecting to see either a NullPointerException or some garbage value [ I will be honest, I didn’t know what to expect].

But as you can see from the above output, Thread “9” also has a value of “KungFu Panda” !! Now where did that come from ?

The culprit is the way in which we are initializing the ThreadLocal. From the code, as you can see its being initialised within a method and its initialValue() is being set with the value that is being passed to the method.

So when thread.start() is invoked, the call hierarchy goes via main() -> Thread.get() -> Thread.initialValue().

But wait a minute !! initialValue() is embedded within the greet() method, so shouldn’t we be seeing greet() method in the call hierarchy ?? Well no. That’s not the case.

For any anonymous objects that define their own implementation and which need access to data members from its enclosing scope, Java mandates that the enclosing scope data members need to be defined as “final”. The reason behind this is that when the anonymous implementation is getting executed, the data member to which it refers to from its enclosing scope shouldn’t get changed.

So when names.get() gets invoked from Thread.start(), Java first tries to retrieve the value of the ThreadLocal variable for that particular thread and if it gets a null value, and if there is an overridden variant of “initialValue” defined, then Java calls that. In our case, the initialValue() is depending on the method parameter “name” from the enclosing scope [ the greet() method is the enclosing scope], so Java remembers the last passed value for the “name” parameter [ which from our example was “KungFu Panda”] and that is why, both the threads end up getting the same value.

Learning :
Never try to define a ThreadLocal’s initialValue() to be dependent on a parameter/data from enclosing scope because Java will remember the last set value for it (or) Null if its not set.

I know this post might sound convoluted, but try executing the below “fixed” code, which rectifies the problem. You can see the difference.

public class ThreadLocalProblem {
    private static ThreadLocal<String> names = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        greet("KungFu Panda");
        Runnable runner = new Runnable() {
            @Override
            public void run() {
                String msg = String.format("The [%d] clan welcomes you [%s] oh great Dragon Warrior!",
                    Thread.currentThread().getId(),names.get());
                System.err.println(msg);
            }
        };
        Thread thread = new Thread(runner);
        thread.start();
        thread.join();

    }

    private static void greet(String name) {
        names.set(name);
        String msg = String.format("The [%d] clan welcomes you [%s] oh great Dragon Warrior!",
            Thread.currentThread().getId(),names.get());
        System.err.println(msg);
    }
}

Now the new output looks like below :

The [1] clan welcomes you [KungFu Panda] oh great Dragon Warrior!
The [9] clan welcomes you [null] oh great Dragon Warrior!

Process finished with exit code 0

Advertisements

Discussion

No comments yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: