A Painless Introduction to Java's ThreadLocal Storage
In my last blog post we explored WeakHashMap, which is based on WeakReference in Java. Now let’s look at some best practices for using another powerful class: ThreadLocal from java.lang, which is also implemented using WeakReference.
What is ThreadLocal? A Simple Example
As its name suggests, a single instance of ThreadLocal can store different values for each thread independently. Therefore, the value stored in a ThreadLocal instance is specific (local) to the current running thread and any other code logic running on the same thread will see the same value, but not the values set on the same instance by other threads. (There are exceptions, like InheritableThreadLocal, which inherits its parent thread’s values by default.)
Let’s consider this example:
We have a TransactionManager class that provides static methods to:
- Start a transaction with a generated ID
- Store that ID as a static field and provide a transaction-ID-getter method to other code logic that needs to know the current transaction ID
In a single-threaded environment, TransactionManager can simply store the ID as a static field and return it as is. However, this will certainly not work in a multiple-threaded environment. Imagine if multiple threads used TransactionManager. Transaction IDs generated by each thread could overwrite each other since there is only one static instance of transaction ID. One may synchronize and block other transactions to avoid overwrites, but this would totally defeat the purpose of having multiple threads.
In order to solve this problem, ThreadLocal provides a very neat solution:
public class TransactionManager {
private static final ThreadLocal<String> context = new ThreadLocal<String>();
public static void startTransaction() {
//logic to start a transaction
//...
context.set(generatedId);
}
public static String getTransactionId() {
return context.get();
}
public static void endTransaction() {
//logic to end a transaction
//…
context.remove();
}
}
A different thread that starts transactions via TransactionManager will get its own transaction ID stored in the context. Any logic within the same thread can call getTransactionId() later on to retrieve the value that belongs to or is local to that thread. So, problem solved!
The Internals of ThreadLocal and How it Works
Let’s drill down a little bit into ThreadLocal’s internals. ThreadLocal is implemented by having a map (a ThreadLocalMap) as a field (with a WeakReference entry) within each thread instance. (There are actually two maps. The second one is used for InheritableThreadLocal, but let’s not complicate things.) The keys of those maps are the corresponding ThreadLocals themselves. Therefore, when a set/get is called on a ThreadLocal, it looks at the current thread, finds the map, and looks up the value with “this” ThreadLocal instance.
Still confused? I certainly am. Let’s look at a real example.
- Code running in Thread 1 calls set() on ThreadLocal instance “A” with value "123"
- Code running in Thread 2 calls set() on ThreadLocal instance “A” with value "234"
- Code running in Thread 1 calls set() on ThreadLocal instance “B” with value "345"
And this is the end result:
Thread 1 (the instance)'s field ThreadLocalMap (m1) has two entries:
Key | Value |
---|---|
ThreadLocal A | "123" |
ThreadLocal B | "345" |
Thread 2 (the instance)’s field ThreadLocalMap (m2) has one entry:
Key | Value |
---|---|
ThreadLocal A | "234" |
Now if some code logic in Thread 1 calls get() on ThreadLocal instance “A”, the ThreadLocal logic will look up the current Thread, which is the instance Thread 1, then access the field ThreadLocalMap of that Thread instance, which is m1, it can then look up the value by using m1.get(this), with “this” as ThreadLocal and the result will be “123″.
Know What to Watch Out For!
Did I hear weak references for ThreadLocal entries? Does that mean I don’t have to clean up? Well, it’s not quite that simple.
First of all, the value object put into the ThreadLocal would not purge itself (garbage collected) if there are no more strong references to it. Instead, the weak reference is done on the thread instance, which means Java garbage collection would clean up the ThreadLocal map if the thread itself is not strongly referenced elsewhere.
So now the question is: when would the thread object get garbage collected?
The answer is: it depends, but always assume the thread is long-running. Two common examples:
- Servlets: The threads that handle servlet requests usually stay alive in the container for the lifetime of the server instance. Code logic that uses ThreadLocal might be referenced indirectly by servlets.
- Thread pooling java.util.concurrent.Executors: Java encourages recycling threads!
A typical usage of Executor introduced in Java 1.5 is that, if ThreadLocal maps are not cleaned up properly after a transaction is done, the next TransactionProcessingTask might inherit values from another previous, unrelated task!
ExecutorService service = Executors.newFixedThreadPool(10);
service.submit(new TransactionProcessingTask());
Be careful with initialization of ThreadLocal. Below is an implementation of a counter by thread. Can you tell what is wrong in the initialization below?
public class Counter {
private static ThreadLocal<Integer> counter = new ThreadLocal<Integer>();
static {
counter.set(0);
}
public int getCountInThread() {
return counter.get();
}
//….
}
The counter would not get initialized correctly! Though the counter is declared as static, it CANNOT be initialized by having a static initializer, since the initializer only runs once when the first thread references the Counter class. When the second thread comes in, it does not run counter.set(0) on that thread, therefore counter.get() returns null instead of 0!
With this in mind, you can probably picture the consequences of not cleaning up after yourself! An operation that runs on a recycled thread might inherit the values from a previous operation on the same thread! Besides, it can also cause memory leaks, since the instance stored in ThreadLocal will never get garbage collected if the thread is alive.
As a rule of thumb, always clean up or reset your ThreadLocal after you have finished your unit of operation! Even though the current code might be simple enough to bypass the clean-ups, it might be adapted and integrated into servlets/thread pooling later on! After all, cleaning up responsibly is always appreciated both in the realms of programming and real life.