Sitemap

Thread Local in Java

4 min readJul 30, 2024

--

Press enter or click to view image in full size
Photo by Anya Chernik on Unsplash

Why and Where we use ThreadLocal :

When an application uses multithreading or the concurrency
code, it comes with the cost of synchronization or locking.
But, it all comes with a great effect on the scalability of the
application. When multiple threads share an object,
synchronization is a way to achieve safety. ThreadLocal is
another way to achieve thread safety. It does not require
synchronization; instead, it eliminates sharing by providing
explicitly a copy of an object to each thread. An object is no
more shared, so there is no requirement of synchronization.
Automatically, it improves scalability and performance of the
application.

Let’s consider a scenario where we have multiple services, each with its own thread, but all services share the same UserDetails using ThreadLocal. This is common in web applications where each service needs access to user-specific information throughout the request lifecycle.

Scenario

Let us consider We have two services:

  1. OrderService: Handles order processing.
  2. PaymentService: Handles payment processing.

Both services need access to the user’s details, which we will manage using ThreadLocal.

Code Example

First, let’s create the UserDetails class and the ThreadLocal holder:

public class UserDetails {
private String userId;
private String userName;

// Getters and setters
public String getUserId() {
return userId;
}

public void setUserId(String userId) {
this.userId = userId;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}
}

UserContextHolder

public class UserContextHolder {
private static final ThreadLocal<UserDetails> userThreadLocal = ThreadLocal.withInitial(UserDetails::new);

public static UserDetails getUserDetails() {
return userThreadLocal.get();
}

public static void setUserDetails(UserDetails userDetails) {
userThreadLocal.set(userDetails);
}

public static void clear() {
userThreadLocal.remove();
}
}

Next, let’s create the services

Get Ramanamuttana’s stories in your inbox

Join Medium for free to get updates from this writer.

OrderService:

public class OrderService {
public void processOrder() {
UserDetails userDetails = UserContextHolder.getUserDetails();
System.out.println("OrderService - Processing order for user: " + userDetails.getUserId() + ", " + userDetails.getUserName());
// Order processing logic
}
}

Payment Service:

public class PaymentService {
public void processPayment() {
UserDetails userDetails = UserContextHolder.getUserDetails();
System.out.println("PaymentService - Processing payment for user: " + userDetails.getUserId() + ", " + userDetails.getUserName());
// Payment processing logic
}
}

Finally, simulate the execution in different threads:

public class ThreadLocalExample {
public static void main(String[] args) {
UserDetails userDetails1 = new UserDetails();
userDetails1.setUserId("user1");
userDetails1.setUserName("Alice");

UserDetails userDetails2 = new UserDetails();
userDetails2.setUserId("user2");
userDetails2.setUserName("Bob");

Runnable task1 = () -> {
UserContextHolder.setUserDetails(userDetails1);
new OrderService().processOrder();
new PaymentService().processPayment();
UserContextHolder.clear();
};

Runnable task2 = () -> {
UserContextHolder.setUserDetails(userDetails2);
new OrderService().processOrder();
new PaymentService().processPayment();
UserContextHolder.clear();
};

Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);

thread1.start();
thread2.start();
}
}

Output:

Output: Order and Payment Services in Thread 1 share same username

Diagrammatic Representation:

Overview

  1. UserContextHolder: Holds the ThreadLocal variable for UserDetails.
  2. Threads: Each thread accesses its own instance of UserDetails via the UserContextHolder.
  3. Services: Both OrderService and PaymentService access the same UserDetails instance within the same thread.
+--------------------------+
| UserContextHolder |
| (ThreadLocal variable) |
+--------------------------+
|
v
+----------------------+ +----------------------+
| Thread 1 | | Thread 2 |
|----------------------| |----------------------|
| | | |
| +-----------------+ | | +-----------------+ |
| | UserDetails 1 | | | | UserDetails 2 | |
| |-----------------| | | |-----------------| |
| | userId = user1 | | | | userId = user2 | |
| | userName = Alice| | | | userName = Bob | |
| +-----------------+ | | +-----------------+ |
| | | | | |
| v | | v |
| +-----------------+ | | +-----------------+ |
| | OrderService | | | | OrderService | |
| +-----------------+ | | +-----------------+ |
| | | | | |
| v | | v |
| +-----------------+ | | +-----------------+ |
| | PaymentService | | | | PaymentService | |
| +-----------------+ | | +-----------------+ |
+----------------------+ +----------------------+

Explanation:

  1. UserContextHolder:

Manages the ThreadLocal variable that holds UserDetails.

2. Thread 1 and Thread 2:

  • Each thread sets its own UserDetails (UserDetails 1 for Thread 1 and UserDetails 2 for Thread 2).

3. OrderService and PaymentService:

  • Both services in each thread access the same UserDetails instance specific to that thread, ensuring consistency and isolation of user data.

Key Points

  • Isolation: Each thread maintains its own user details, ensuring data isolation.
  • Consistency: Services within the same thread share the same user details, maintaining consistency.
  • Cleanup: UserContextHolder.clear() is used to remove the user details from the thread after processing is complete to avoid memory leaks.

This example demonstrates how ThreadLocal can be effectively used to manage user-specific data across multiple services within the same thread.

--

--