[Java Concurrency] ThreadLocal Tutorial

Overview

ThreadLocal provides a mechanism where multiple threads can store their own data on a single ThreadLocal object.

You can think of ThreadLocal as a bank. Multiple can go to a single bank, deposit, and withdraw money. It is also the analogy of this tutorial.

ThreadLocal Quick Example

Using the bank analogy, let’s say there are two people, Joe and Jimmy both go to the bank and deposit money. After a while, they withdraw some.

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        var gorillaBank = new ThreadLocal<Integer>();

        new Thread(() -> {
            System.out.println("Joe deposits $1000");
            gorillaBank.set(1000);

            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("Joe about to withdraw $500");
            gorillaBank.set(gorillaBank.get() - 500);

            System.out.println("Joe's balance: " + gorillaBank.get());
        }).start();

        new Thread(() -> {
            System.out.println("Jimmy deposits $2000");
            gorillaBank.set(2000);

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("Jimmy about to withdraw $500");
            gorillaBank.set(gorillaBank.get() - 500);

            System.out.println("Jimmy's balance: " + gorillaBank.get());
        }).start();
    }

Running the code above produces this output:

ThreadLocal example output
ThreadLocal example output

As you can see, even though the gorillaBank object was accessed by two threads, data for each thread is separated and we got the expected results.

You can visualize the process as this diagram:

Example diagram. Joe and Jimmy deposit and withdraw money from thread-local bank
Example diagram. Joe and Jimmy deposit and withdraw money from the thread-local bank

Operations

There are some common (not all) operations you can execute on a ThreadLocal object.

Set

This method set the value on the ThreadLocal object

gorillaBank.set(1000);

Get

This method gets the current value

gorillaBank.get();

Remove

This method removes the value on the ThreadLocal object

    public static void main(String[] args) {
        var gorillaBank = new ThreadLocal<Integer>();
        new Thread(() -> {
            System.out.println("Joe deposits $1000");
            gorillaBank.set(1000);

            System.out.println("Joe's balance: " + gorillaBank.get());

            gorillaBank.remove();

            System.out.println("Joe's balance: " + gorillaBank.get());
        }).start();

    }

After removing the value, get method returns null

ThreadLocal remove method
ThreadLocal remove method

Initial value

Let’s say the bank is new and they run a promotion to give $1000 to people who open accounts. With ThreadLocal, you can use the static method withInitial:

    public static void main(String[] args) {
        var gorillaBank = ThreadLocal.withInitial(() -> 1000);
        new Thread(() -> {
            System.out.println("Joe deposits $1000");
            gorillaBank.set(gorillaBank.get() + 1000);

            System.out.println("Joe's balance: " + gorillaBank.get());
        }).start();

    }

This code output:

Joe deposits 1000 but his account now has 2000 because the bank initiated with 1000
Joe deposits 1000 but his account now has 2000 because the bank initiated with 1000

Inherited ThreadLocal

When you want share the value from a thread with threads spawning within it, you can use InheritableThreadLocal.

Let’s see an example:

    public static void main(String[] args) {
        var gorillaBank = new InheritableThreadLocal<Integer>();
        var oldBank = ThreadLocal.withInitial(() -> 0);
        oldBank.set(3000);
        gorillaBank.set(1000);
        new Thread(() -> {
            System.out.println("Joe deposits $1000 at gorilla bank");
            gorillaBank.set(gorillaBank.get() + 1000);

            System.out.println("Joe deposits $1000 at old bank");
            oldBank.set(oldBank.get() + 1000);

            System.out.println("Joe's balance at gorilla bank: " + gorillaBank.get());
            System.out.println("Joe's balance at old bank: " + oldBank.get());
        }).start();

    }

Here I created two ThreadLocal objects, one is the old-school ThreadLocal and the other is InheritableThreadLocal. The new thread is a child thread of the main thread.

In the code, Joe deposited to both Gorilla bank and Old bank.

Here is the output:

InheritableThreadLocal vs ThreadLocal
InheritableThreadLocal vs ThreadLocal

As you can see, since gorillaBank is an InheritableThreadLocal instance, the thread that spawned inside Main can see the changes set by the main thread.

In this case, when Joe deposits 1000 to his account and Gorilla Bank, his balance is 2000. However, the old bank is a regular ThreadLocal instance, no value is passed on inner threads. In fact, I need to set it to the initial value of 0 to avoid an exception being thrown when calling oldbank.get()

Cautions

ThreadLocal ensures privacy per thread, not per task. For example, if you run multiple tasks on a single thread, all the tasks can see and mutate the same thread local value.

Let’s see an example:

     public static void main(String[] args) {
        var gorillaBank = ThreadLocal.withInitial(() -> 0);

        var executor = Executors.newSingleThreadExecutor();

        for (int i =0; i < 5; i++) {
            executor.submit(() -> {
                System.out.println("Anonymous user donates $1000");
                gorillaBank.set(gorillaBank.get() + 1000);

                System.out.println("Charity's balance: " + gorillaBank.get());
            });
        }

        executor.shutdown();
     }

Here is the code’s output:

Multiple tasks access ThreadLocal on a single thread
Multiple tasks access ThreadLocal on a single thread

Here I created an executor using the static method newSingleThreadExecutor to make sure all the tasks will run in a single thread. As you can see, subsequent tasks can see the values set by previous tasks. This is not necessarily a bad thing. You just need to be aware of this behavior.

Conclusion

In this tutorial, I demonstrated the usage of the ThreadLocal object. All the code is available here on Github

Leave a Comment