Today, one of the most critical aspects of a concurrent application is shared data. When you create a thread that implements the Runnable
interface and then starts various Thread
objects using the same Runnable
object. All the threads share the same attributes that are defined inside the runnable object.
This essentially means that if you change any attribute in a thread, all the threads will be affected by this change and will see the modified value by the first thread. Sometimes it is desired behavior e.g. multiple threads increasing/decreasing the same counter variable, but sometimes you want to ensure that every thread MUST work on its copy of thread instance and does not affect others’ data.
private static final ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 1);
Runnable task1 = () -> {
threadLocalValue.set(100);
// do some work
System.out.println("Value: " + threadLocalValue.get());
};
Runnable task2 = () -> {
threadLocalValue.set(200);
// do some work
System.out.println("Value: " + threadLocalValue.get());
};
Thread thread1 = new Thread(task1, "Thread-1");
Thread thread2 = new Thread(task2, "Thread-2");
thread1.start(); // Prints 100
thread2.start(); // Prints 200
1. When to use ThreadLocal?
For example, consider you are working on an eCommerce application. You have a requirement to generate a unique transaction ID for every customer request in the controller process. You need to pass this transaction ID to the business methods in manager/DAO classes for logging purposes.
One solution could be passing this transaction ID as a parameter to all the business methods. But this is not a good solution as the code is redundant and unnecessary.
To solve that, here you can use ThreadLocal
variable. You can generate a transaction ID in the controller OR any pre-processor interceptor and set this transaction ID in the ThreadLocal
. After this, whatever the methods, that this controller calls, they all can access this transaction ID from the ThreadLocal.
Also note that the application controller will be servicing more than one request at a time and since each request is processed in a separate thread at the framework level, the transaction id will be unique to each thread and will be accessible from all over the thread’s execution path.
Related: Share context data with JAX-RS ResteasyProviderFactory (ThreadLocalStack Example)
2. How does ThreadLocal class work?
The Java Concurrency API provides a clean mechanism for thread-local variables using ThreadLocal class with a very good performance.
public class ThreadLocal<T> extends Object {...}
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. 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).
Internally, each thread has a ThreadLocalMap object that stores the thread-local variables. Each thread maintains its own map of ThreadLocal
variables to their respective values.
public class ThreadLocal<T> {
/** ThreadLocalMap is a customized hash map suitable only for maintaining thread local values.
* No operations are exported outside of the ThreadLocal class. ...
*/
static class ThreadLocalMap {
private Entry[] table;
// a HashMap similar implemetation
}
//...
}
The ThreadLocal class has the following methods to put and get the values:
- get(): Returns the value in the current thread’s copy of this thread-local variable.
- initialValue(): Returns the current thread’s “initial value” for this thread-local variable.
- remove(): Removes the current thread’s value for this thread-local variable.
- set(T value): Sets the current thread’s copy of this thread-local variable to the specified value.
3. How to use ThreadLocal?
The below example uses two thread-local variables i.e. threadId and startDate. Both have been defined as “private static” fields as recommended. The ‘threadId‘ will be used to identify the thread which is currently running and ‘startDate‘ will be used to get the time when the thread started its execution.
The above information will be printed in the console to verify that each thread has maintained its copy of variables.
class DemoTask implements Runnable {
// 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 int getThreadId() {
return threadId.get();
}
// Returns the current thread's starting timestamp
private static final ThreadLocal<Date> startDate = new ThreadLocal<Date>() {
protected Date initialValue() {
return new Date();
}
};
@Override
public void run() {
System.out.printf("Starting Thread: %s : %s\n", getThreadId(), startDate.get());
try {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("Thread Finished: %s : %s\n", getThreadId(), startDate.get());
}
}
Now to verify that variables essentially can maintain their state irrespective of multiple initializations for multiple threads, let’s create three instances of this task; start the threads; and then verify the information they print in the console.
Starting Thread: 0 : Wed Dec 24 15:04:40 IST 2014
Thread Finished: 0 : Wed Dec 24 15:04:40 IST 2014
Starting Thread: 1 : Wed Dec 24 15:04:42 IST 2014
Thread Finished: 1 : Wed Dec 24 15:04:42 IST 2014
Starting Thread: 2 : Wed Dec 24 15:04:44 IST 2014
Thread Finished: 2 : Wed Dec 24 15:04:44 IST 2014
In the above output, the sequence of the printed statements will vary every time. I have put them in sequence so that we can identify that thread local values are kept safe for each thread instance; and never intermixed. Try yourself.
Most common use of thread local is when you have some object that is not thread-safe, but you want to avoid synchronizing access to that object using synchronized keyword/block. Instead, give each thread its own instance of the object to work with.
A good alternative to synchronization or threadlocal is to make the variable a local variable. Local variables are always thread safe. The only thing which may prevent you to do this is your application design constraints.
In wabapp server, it may be keep a thread pool, so a ThreadLocal var should be removed before response to the client, since current thread may be reused by next request.
Also, if you do not clean up when you’re done, any references it holds to classes loaded as part of a deployed webapp will remain in the permanent heap and will never get garbage collected.
Happy Learning !!
Comments