Thread-Local Variables in Java

Threads - images of multicoloured threads

In the next few posts we’ll be looking at some concurrency related topics. In the last post we looked at escaping object references. In this post we’ll look at ThreadLocal variables in Java.

Introduction to ThreadLocal Variables

ThreadLocal variables were introduced in Java 1.2. They have been a standard mechanism for sharing data across methods within the same thread.

ThreadLocal variables are usually created as private static fields in classes that need to associate some state (such as a user ID or transaction ID) with a specific thread. While it looks like an normal variable, a ThreadLocal variable has one current value per thread.

Each thread that accesses a ThreadLocal variable via the get() or set() methods has its own, independently initialized copy of that variable. We can think of this as a ThreadLocal variable storing data inside an internal map using the thread itself as the key. The actual value of the data depends on which thread calls its get() or set() methods to read or write the value.

Creating ThreadLocal Variables

TheadLocal variables allow us to store data that is only accessible by a specific thread. We can create a ThreadLocal variable using its parameterized constructor as follows:

ThreadLocal<Integer> local = new ThreadLocal<>();

When first created, a ThreadLocal variable has an initial value of null.

We can also create a ThreadLocal by using the static withInitial() factory method and passing a Supplier to it:

ThreadLocal<Integer> local = ThreadLocal.withInitial( () -> 123 );

Accessing ThreadLocal Values

We can give a ThreadLocal variable an initial value either by calling its set() method, or by overriding its initialValue() method. This is easy to do in an anonymous inner class as follows:

ThreadLocal<Integer> local = new ThreadLocal<>() {
                                  @Override 
                                  protected Integer initialValue() {
                                     return new Integer(123);
                                  }
                            };

The initialValue() method will be run the first time a thread calls the get() method. However, if the thread calls the set() method before calling get(), then the initialValue() method will not be invoked.

We can delete the value from the ThreadLocal variable by calling the remove() method. If the variable is read again using the get() method, the initialValue() method will be called again.

Example

The example code below has two static/class variables. One is a normal static variable shared by all instances. Any updates to this shared variable can be seen by all the other instances. The other is a static ThreadLocal variable. Each thread can get/set its own values independent of all the other runnable objects.

Both variables are of type AtomicInteger. These are mutable objects (unlike the usual class wrappers such as Integer). The class provides atomic arithmetic operations such as increment, decrement, add and accumulate, as well as the usual java.lang.Number methods.

import java.util.concurrent.atomic.AtomicInteger;

public class RunnableWithThreadLocalAndShared implements Runnable {

    // This static variable is shared between all instances.
    private static AtomicInteger shared = new AtomicInteger(1000);

    // This is also a static variable, but because it is a ThreadLocal, 
    // each thread can get/set its own values independent of all the 
    // other runnable objects.
    private static ThreadLocal<AtomicInteger> local = new ThreadLocal<>();

    // an initial value for each instance
    private final int initial;

    // constructor for the initial value
    public RunnableWithThreadLocalAndShared(int initial) {
        this.initial = initial;
    }

    @Override
    public void run() {
        System.out.println("Shared value before increment: " + shared);
        shared.incrementAndGet();
        System.out.println("Shared value after  increment: " + shared);

        local.set(new AtomicInteger(initial)); 
        System.out.println("Local  value before increment: " + local.get());
        local.set(new AtomicInteger(local.get().incrementAndGet()));
        System.out.println("Local  value after  increment: " + local.get());
    }
} // end of class

In the Test class, we create a number of these objects, pass them through to the Thread constructor and start them up.

public class Test {

    public static void main(String args[]) {

        new Thread(new RunnableWithThreadLocalAndShared(2000)).start();
        new Thread(new RunnableWithThreadLocalAndShared(3000)).start();
        new Thread(new RunnableWithThreadLocalAndShared(4000)).start();
        new Thread(new RunnableWithThreadLocalAndShared(5000)).start();
    }
} // end of class

From the output we can see that the shared static/class variable is updated by each thread, while the ThreadLocal variables are updated independently. Your output might differ in sequence and order, but by the end of the run, the shared value will have been incremented four times, while each local value will only have been incremented once.

Shared value before increment: 1000
Shared value after  increment: 1001
Local  value before increment: 2000
Local  value after  increment: 2001
Shared value before increment: 1001
Shared value after  increment: 1002
Local  value before increment: 3000
Local  value after  increment: 3001
Shared value before increment: 1002
Shared value after  increment: 1003
Local  value before increment: 4000
Local  value after  increment: 4001
Shared value before increment: 1003
Shared value after  increment: 1004
Local  value before increment: 5000
Local  value after  increment: 5001

Problems

Notwithstanding the established use of ThreadLocal variables, there are a number of problems associated with using them.

  • Unconstrained mutability. Every ThreadLocal variable is mutable. This allows any code to change the variable’s value at any time. This can cause potential problems with spaghetti-like data flow.

  • Unbounded lifetime. Values can persist longer than necessary. This can result in memory leaks if the remove() method isn’t called. In particular, if thread pools are used, the value of a ThreadLocal variable set in one thread could accidentally leak into an unrelated thread.

  • Expensive inheritance. Inheriting ThreadLocal variables across threads adds significant overhead. ThreadLocal variables in a parent thread can be inherited by child threads. Each child thread then needs to allocate space for ThreadLocal variables used in the parent thread.

Further Reading and Signing Off

For further details, we should read the online ThreadLocal API documentation. Java 8 documentation is here, and Java 17 docs are here.

For more on the problems with ThreadLocal variables, see JEP 481: Scoped Values (Third Preview).

Lots more on concurrency to come!

Was this useful? Please share your comments, and as always, stay safe and keep learning!

Leave a Comment

Your email address will not be published. Required fields are marked *

Thank You

We're Excited!

Thank you for completing the form. We're excited that you have chosen to contact us about training. We will process the information as soon as we can, and we will do our best to contact you within 1 working day. (Please note that our offices are closed over weekends and public holidays.)

Don't Worry

Our privacy policy ensures your data is safe: Incus Data does not sell or otherwise distribute email addresses. We will not divulge your personal information to anyone unless specifically authorised by you.

If you need any further information, please contact us on tel: (27) 12-666-2020 or email info@incusdata.com

How can we help you?

Let us contact you about your training requirements. Just fill in a few details, and we’ll get right back to you.