Have you ever wondered how it’s possible that we can have thread locals just by using ThreadLocal class?
Introduction
We create ThreadLocal object and magically this instance has one value but at the same time many values because each thread has its own one. Maybe this class has native methods? No, the answer is much simpler.
ThreadLocal usage
First of all, how to use ThreadLocal?
class MyRunnableImpl implements Runnable {
private static final ThreadLocal<Integer> INTEGER_THREAD_LOCAL = new ThreadLocal<>();
private final String threadName;
private final int number;
MyRunnableImpl(String threadName, int number) {
this.threadName = threadName;
this.number = number;
}
@Override
public void run() {
INTEGER_THREAD_LOCAL.set(number);
try {
for (int i = 0; i < 10; i++) {
System.out.println(threadName + " -> " + INTEGER_THREAD_LOCAL.get());
Thread.sleep(1000);
INTEGER_THREAD_LOCAL.set(INTEGER_THREAD_LOCAL.get() + 1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
new Thread(new MyRunnableImpl("A", 0)).start();
new Thread(new MyRunnableImpl("B", 10)).start();
new Thread(new MyRunnableImpl("C", 100)).start();
}
}
Result:
B -> 10
C -> 100
A -> 0
B -> 11
C -> 101
A -> 1
B -> 12
C -> 102
A -> 2
C -> 103
A -> 3
B -> 13
C -> 104
A -> 4
B -> 14
B -> 15
A -> 5
C -> 105
C -> 106
B -> 16
A -> 6
B -> 17
A -> 7
C -> 107
B -> 18
A -> 8
C -> 108
A -> 9
B -> 19
C -> 109
As we can see the order of execution isn’t deterministic but the result is. Thanks to ThreadLocal in every thread there is a different value and the values don’t collide with each other.
Let’s look under the hood
If we look at the ThreadLocal#set implementation we see this:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
Of course at the beginning value of T is passed as method parameter where T is a generic type of ThreadLocal class. Then current thread is saved to local variable and getMap method is called that in turn returns ThreadLocalMap instance. The map is store for all local variables that are identified by ThreadLocalMap instance acting as a key.
Ok, but how is the ThreadLocalMap able to store local variables?
When we look at the ThreadLocal#getMap implementation, we see something interesting:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
The ThreadLocalMap instance is stored in thread what explains why having one ThreadLocal instance actually means one value per thread. The truth is that the whole information comes from thread. And this is the code for getting value in ThreadLocal:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
And setInitialValue method:
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);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}
Interesting here is setInitialValue call in the end of get method. This means that ThreadLocal value is loaded lazily if there is any initial value because when we look at the constructor we see nothing:
public ThreadLocal() {
}
So as we can guess initialValue method returns null by default:
protected T initialValue() {
return null;
}
However we can set initial value using ThreadLocal#withInitial static method which in turn returns SuppliedThreadLocal instance having this method implemented:
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<? extends T> supplier;
SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
@Override
protected T initialValue() {
return supplier.get();
}
}
Conclusion
As we can see there is no magic in this code. No magic? Actually there is a little magic. We didn’t look at the Thread#currentThread implementation which makes it possible to create such a mechanism. However even if we tried to look there, we wouldn’t had seen anything. That’s because this method is native so there is no Java implementation at all. If you want to read more about native methods go here. Hope you enjoyed.