Today we are going to look into the ThreadLocal internals, its usage and all the related pitfalls.
ThreadLocal, among other Java advanced features like volatile variables or nested classes, is rarely used.
The main reason is its relative complexity and mostly the very few cases where ThreadLocal is really required.
Yet, if its underlying mechanism is well understood, ThreadLocal can be very handy in some situations. No wonder it is at the heart of some framework features, Spring
TransactionSynchronizationManager for example.
I Usage
A Global design
ThreadLocal is an alternative to the usage of global variable passed from methods to methods as argument (brute force technique) or static variable (visible within the current ClassLoader).
However, unlike global or static variable, the ThreadLocal scope is limited to the execution of the current thread, thus its name. A ThreadLocal attached to Thread1 cannot be access by Thread2 and vice versa. This thread isolation makes ThreadLocal thread-safe by nature, unlike static variables which need synchronization (thus performance overhead)
A ThreadLocal is composed of the ThreadLocal object itself and a value Object we’ll refer to as target Object for the rest of this article.
A ThreadLocal object is created, then attached to the current thread. All portion of your program executed by the current thread can access the ThreadLocal target Object, provided that the code can access the ThreadLocal reference, and that’s the trick! There is no point creating a ThreadLocal if it cannot be accessed everywhere in your code.
Most of the time, the ThreadLocal object itself is created as a static final variable. Static to make it accessible from everywhere and final to avoid being modified. There is no need to synchronize the access to the ThreadLocal object itself since only the target Object is usefull and this object is different from one thread to another (so thread-safe by nature)
B Example
package test; public class ThreadLocalManager { public static final ThreadLocal myThreadLocal = new ThreadLocal(); } public class TestThreadLocal implements Runnable { private String value; private long delayTime; private long sleepTime; public TestThreadLocal(String value, long delayTime, long sleepTime) { this.value = value; this.delayTime = delayTime; this.sleepTime = sleepTime; } @Override public void run() { try { Thread.sleep(this.delayTime); System.out.println("[" + this + "] is setting myThreadLocal [" + ThreadLocalManager.myThreadLocal + "] the value : " + this.value); ThreadLocalManager.myThreadLocal.set(this.value); Thread.sleep(this.sleepTime); System.out.println("[" + this + "] is accessing myThreadLocal [" + ThreadLocalManager.myThreadLocal + "] value : " + ThreadLocalManager.myThreadLocal.get()); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { TestThreadLocal test1 = new TestThreadLocal("V1", 0, 200); System.out.println("Creating test1 : " + test1); TestThreadLocal test2 = new TestThreadLocal("V2", 100, 500); System.out.println("Creating test2 : " + test2); Thread t1 = new Thread(test1); Thread t2 = new Thread(test2); t1.start(); t2.start(); } }
The produced output is:
Creating test1 : test.TestThreadLocal@30c221
Creating test2 : test.TestThreadLocal@1e5e2c3
[test.TestThreadLocal@30c221] is setting myThreadLocal [java.lang.ThreadLocal@f72617] the value : V1
[test.TestThreadLocal@1e5e2c3] is setting myThreadLocal [java.lang.ThreadLocal@f72617] the value : V2
[test.TestThreadLocal@30c221] is accessing myThreadLocal [java.lang.ThreadLocal@f72617] value : V1
[test.TestThreadLocal@1e5e2c3] is accessing myThreadLocal [java.lang.ThreadLocal@f72617] value : V2
Although the execution of both threads is tangled, the output message clearly show that the ThreadLocal value is completely isolated between threads t1 & t2.
II Code analysis
A Class diagram
Below is a class diagram of all objects related to ThreadLocal. This diagram has been designed reversing from openjdk 6-b14 source code.
ThreadLocalMap is a static inner class inside ThreadLocal class
ThreadLocalMap.Entry is a static inner class inside ThreadLocalMap class and extending WeakReference class
A Thread has a package protected threadLocals variable of type ThreadLocal.ThreadLocalMap
A ThreadLocalMap has a private table variable which is an array of Entry objects
The ThreadLocal class itself is not extending nor aggregating any above class. Thus it is in relation with Thread and ThreadLocalMap classes
The ThreadLocal class exposes the following methods:
- get(): get the target Object of this ThreadLocal from the current thread. get() may return different objects if called withing different threads
- set(T value): set the target Object of this ThreadLocal to the current thread
- initialValue(): returns the initial target Object of this ThreadLocal. The default implementation returns null. You can make your own ThreadLocal class (extending the default class) returning a non null initial value
- setInitialValue(T value): set the initial target Object of this ThreadLocal to the current thread. This is merely a call to initialValue() followed by a registration of this value into the current thread ThreadLocaLMap
- createMap(Thread t,T firstValue): initialize a ThreadLocaLMap object for the given thread t
- getMap(Thread t): retrieve the ThreadLocaLMap object from the given thread t
B Source code
1) java.lang.Thread
public class Thread implements Runnable { … ThreadLocal.ThreadLocalMap threadLocals = null; … }
2) java.lang.ThreadLocal
public class ThreadLocal { /*********************************** SET CASE *************************/ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } /*********************************** GET CASE *************************/ public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = t.threadLocals; if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); } private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } protected T initialValue() { return null; } /*********************************** REMOVE CASE *************************/ public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } ... ... static class ThreadLocalMap { static class Entry extends WeakReference { Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } } private void set(ThreadLocal key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); ... }
The first time set(Object targetObject) is called, if the ThreadLocalMap of the current thread does not exist, it is created and then the target Object is set to this map using the ThreadLocal object (static instance) as search key.
The first time get() is called, if the ThreadLocalMap of the current thread exists, the target Object is returned using current ThreadLocal instance as search key. Otherwise a new ThreadLocalMap object is created for current thread and default initial value is set.
As we can see, the static instance of ThreadLocal is used only as search key. Its value cannot be changed because it is declared final. The ThreadLocal instance is totally thread-safe because it is really read-only so synchronization is not requited.
What is potentially not thread-safe is the target Object. But since it is put in the ThreadLocalMap of the current thread (package protected access) there is no risk that other threads modify its state.
The ThreadLocalMap class contains the static inner class Entry, which is just a key-value structure to store the target Object.
What is interesting is the implementation of set(ThreadLocal threadLocal, Object value) method of the ThreadLocalMap class.
Before inserting a new value into the Entry[] table, the table is scanned to check whether the key already exist. If so the old target Object is replaced by the new one.
If one key is null (because garbaged by GC as weak reference), then Entry is considered “staled” and the corresponding target Object is de-referenced. The same Entry instance is re-used to store the current ThreadLocal/target Object pair.
III Memory leak
The major issue with ThreadLocal is the potential risk of memory leak. Indeed, the ThreadLocalMap contains an array of Entry whose key value extends WeakReference so one can naively think that it will be garbaged by GC automatically when memory is low. But the fact is that only the key (ThreadLocal object) is a weak reference, not the target object itself !
So in case the ThreadLocal object is garbaged, the Entry object still holds a strong reference to the target Object and it is the main source of memory leak.
For sure, the set(ThreadLocal threadLocal, Object value) method has a mechanism to detect “staled” objects and remove them but this cleaning process is trigger only by the call to this method.
The best way to avoid memory leak is to call remove() method on the ThreadLocal instance once the job is done. This will remove the ThreadLocal key as well as the target Object in the ThreadLocalMap of the current thread.
Calling set(null) on the ThreadLocal instance is also fine since it de-reference the target Object but the ThreadLocal object itself will remain in the ThreadLocalMap as key. This is not a big issue since the reference to this ThreadLocal object is a weak reference as per implementation.
Pingback: ThreadLocal explained | Wanderer
ThreadLocal variable in Java can create serious memory leak in Java web application, if it is used to store instances of classes which is loaded by a classloader other than BootStrapClassLoader. For example, stroing Java API classes on ThreadLocal will not cause memory leak, but application class, which is loaded by web-app classloader will.
Hello Rohit
You’re perfectly right about memory leak issues. But I think it’s more related to multiple classloader pattern than to usage of Thread local.
You can easily create memory leaks using a simple HashMap and instances from different class loaders