Engineering, Monitoring

An Introduction to Java’s ThreadLocal Storage

Posted by

Patson Luk

What is ThreadLocal?

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. 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, though, like InhertiableThreadLocal, which inherits parent threads’ values by default.

Let’s consider the following 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 as is. However, this will certainly not work in a multiple-threaded environment. Imagine that multiple threads are using TransactionManager. In this case, transaction IDs generated by each thread can overwrite each other because 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.

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 to retrieve the value belongs/local to that thread. Problem solved!

The Internals of ThreadLocal and How it Works

Let’s drill down a little bit into the ThreadLocal’s internals. ThreadLocal is implemented by having a map (a ThreadLocalMap) as field (with WeakReference entry) within each thread instance. (There are actually two maps; the second one is used for InheritabeleThreadLocal, but let’s not complicate the picture.) The map keys 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 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 is “123.″

What to Watch Out For

Did I hear a weak reference 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 will 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 will clean up the ThreadLocal map if the thread itself is not strongly referenced elsewhere.

So now the question becomes, “When would the thread object get garbage collected?”

The answer? It depends, but always assume the thread is long running. Here are two common examples:

  1. 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.
  2. Thread pooling java.util.concurrent.Executors. Java encourages recycling threads!

A typical use of Executor, which was introduced in Java 1.5, if ThreadLocal maps are not cleaned up properly after a transaction is complete, the next TransactionProcessingTask might inherit values from another previous unrelated task!

ExecutorService service = Executors.newFixedThreadPool(10);

service.submit(new TransactionProcessingTask());

Be careful with initialization in ThreadLocal. The following is an implementation of a counter by thread. Can you tell what is wrong in the following initialization?

public class Counter {
   private static ThreadLocal<Integer> counter
=
   new ThreadLocal<Integer>();
static {
   counter.set(0);
}
public int getCountInThread() {
   return counter.get();
}
//…
}

The counter will not get initialized correctly! Though the counter is declared as static, it CANNOT be initialized by having a static initializer, as 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 zero! One solution is to subclass ThreadLocal and override the initialValue() method to assign non-null initial value.

With these in mind, you can probably imagine the consequences of not cleaning up after yourself! An operation that runs on a recycled thread might inherit the values of a previous operation on the same thread! Besides, it can also cause memory leaks because the instance stored in ThreadLocal will never get garbage collected if the thread is alive.

As a rule of thumb, always clean up/reset your ThreadLocal after you have finished your unit of operation. Even though the current code might be simple enough to bypass the cleanups, it might be adapted and integrated into servlets/thread-pooling later on! After all, cleaning up responsibly is always appreciated in programming and real life.

© 2018 SolarWinds Worldwide, LLC. All rights reserved.