ThreadLocal Class in Java

Java ThreadLocal is used to create thread-local variables. We know that all threads of an Object share its variables, so the variable is not thread safe. We can use synchronization for thread safety but if we want to avoid synchronization, we can use ThreadLocal variables.
The TheadLocal construct allows us to store data that will be accessible only by a specific thread.
ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

ThreadLocal Class Constructors

  • ThreadLocal() - Creates a thread local variable.

ThreadLocal Class Methods


  • T get() - Returns the value in the current thread's copy of this thread-local variable.
  • protected T initialValue() - Returns the current thread's "initial value" for this thread-local variable.
  • void remove() - Removes the current thread's value for this thread-local variable.
  • void set(T value) - Sets the current thread's copy of this thread-local variable to the specified value.
  • static ThreadLocal withInitial(Supplier<? extends S> supplier) - Creates a thread local variable.

ThreadLocal Class Example

In this example, the class below generates unique identifiers local to each thread. A thread's id is assigned the first time it invokes ThreadId.get() and remains unchanged on subsequent calls.
import java.util.concurrent.atomic.AtomicInteger;

public class ThreadLocalExample {
 // Atomic integer containing the next thread ID to be assigned
    private static final AtomicInteger nextId = new AtomicInteger(0);

    // Thread local variable containing each thread's ID
    private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
    @Override
         protected Integer initialValue() {
              return nextId.getAndIncrement();
         }
    };

    // Returns the current thread's unique ID, assigning it if necessary
    public static int get() {
        return threadId.get();
    }
}
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

ThreadLocal Real-World Example

In real Java applications, we use ThreadLocal to store the login user Context instance. Each thread will have its own ThreadLocal instance.
In our example, we have a dedicated thread for each particular userId and this thread is created by us so we have full control over it.
The run() method will fetch the user context and store it into the ThreadLocal variable using the set() method: 
Let's first create a Context class which contains userName.

Context.java

public class Context {
    private final String userName;

    Context(String userName) {
        this.userName = userName;
    }

    @Override
    public String toString() {
        return "Context{" +
          "userNameSecret='" + userName + '\'' +
          '}';
    }
}

UserRepository.java

public class UserRepository {
    String getUserNameForUserId(Integer userId) {
        return UUID.randomUUID().toString();
    }
}

ThreadLocalWithUserContext.java

public class ThreadLocalWithUserContext implements Runnable {
   
    private static final ThreadLocal<Context> userContext = new ThreadLocal<>();
    private final Integer userId;
    private UserRepository userRepository = new UserRepository();

    ThreadLocalWithUserContext(Integer userId) {
        this.userId = userId;
    }


    @Override
    public void run() {
        String userName = userRepository.getUserNameForUserId(userId);
        userContext.set(new Context(userName));
        System.out.println("thread context for given userId: " + userId + " is: " + userContext.get());
    }
}

ThreadLocalTest.java

We can test it by starting two threads that will execute the action for a given userId:
public class ThreadLocalTest{

     public static void main(String []args){
        ThreadLocalWithUserContext firstUser  = new ThreadLocalWithUserContext(1);
        ThreadLocalWithUserContext secondUser  = new ThreadLocalWithUserContext(2);
        new Thread(firstUser).start();
        new Thread(secondUser).start();
     }
}
After running this code we’ll see on the standard output that ThreadLocal was set per given thread:
Output:
thread context for given userId: 1 is: Context{userNameSecret='18a78f8e-24d2-4abf-91d6-79eaa198123f'}
thread context for given userId: 2 is: Context{userNameSecret='e19f6a0a-253e-423e-8b2b-bca1f471ae5c'}
We can see that each of the users has its own Context.

Reference

Comments