Table of Contents
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:
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:
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
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:
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:
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:
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
I build softwares that solve problems. I also love writing/documenting things I learn/want to learn.