[ACCEPTED]-How is Java's ThreadLocal implemented under the hood?-thread-static

Accepted answer
Score: 125

All of the answers here are correct, but 72 a little disappointing as they somewhat 71 gloss over how clever ThreadLocal's implementation 70 is. I was just looking at the source code for ThreadLocal and was 69 pleasantly impressed by how it's implemented.

The Naive Implementation

If 68 I asked you to implement a ThreadLocal<T> class given 67 the API described in the javadoc, what would 66 you do? An initial implementation would 65 likely be a ConcurrentHashMap<Thread,T> using Thread.currentThread() as its key. This will 64 would work reasonably well but does have 63 some disadvantages.

  • Thread contention - ConcurrentHashMap is a pretty smart class, but it ultimately still has to deal with preventing multiple threads from mucking with it in any way, and if different threads hit it regularly, there will be slowdowns.
  • Permanently keeps a pointer to both the Thread and the object, even after the Thread has finished and could be GC'ed.

The GC-friendly Implementation

Ok try again, lets deal 62 with the garbage collection issue by using 61 weak references. Dealing with WeakReferences can be confusing, but 60 it should be sufficient to use a map built 59 like so:

 Collections.synchronizedMap(new WeakHashMap<Thread, T>())

Or if we're using Guava (and we should 58 be!):

new MapMaker().weakKeys().makeMap()

This means once no one else is holding 57 onto the Thread (implying it's finished) the 56 key/value can be garbage collected, which 55 is an improvement, but still doesn't address 54 the thread contention issue, meaning so 53 far our ThreadLocal isn't all that amazing of a class. Furthermore, if 52 someone decided to hold onto Thread objects after 51 they'd finished, they'd never be GC'ed, and 50 therefore neither would our objects, even 49 though they're technically unreachable now.

The Clever Implementation

We've 48 been thinking about ThreadLocal as a mapping of threads 47 to values, but maybe that's not actually 46 the right way to think about it. Instead 45 of thinking of it as a mapping from Threads 44 to values in each ThreadLocal object, what 43 if we thought about it as a mapping of ThreadLocal 42 objects to values in each Thread? If each thread stores 41 the mapping, and ThreadLocal merely provides 40 a nice interface into that mapping, we can 39 avoid all of the issues of the previous 38 implementations.

An implementation would 37 look something like this:

// called for each thread, and updated by the ThreadLocal instance
new WeakHashMap<ThreadLocal,T>()

There's no need 36 to worry about concurrency here, because 35 only one thread will ever be accessing this 34 map.

The Java devs have a major advantage 33 over us here - they can directly develop 32 the Thread class and add fields and operations 31 to it, and that's exactly what they've done.

In 30 java.lang.Thread there's the following lines:

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

Which as the 29 comment suggests is indeed a package-private 28 mapping of all values being tracked by ThreadLocal objects 27 for this Thread. The implementation of ThreadLocalMap is not 26 a WeakHashMap, but it follows the same basic contract, including 25 holding its keys by weak reference.

ThreadLocal.get() is then 24 implemented like so:

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 ThreadLocal.setInitialValue() like so:

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;
}

Essentially, use 23 a map in this Thread to hold all our ThreadLocal objects. This way, we 22 never need to worry about the values in 21 other Threads (ThreadLocal literally can only access 20 the values in the current Thread) and therefore 19 have no concurrency issues. Furthermore, once 18 the Thread is done, its map will automatically 17 be GC'ed and all the local objects will 16 be cleaned up. Even if the Thread is held onto, the 15 ThreadLocal objects are held by weak reference, and 14 can be cleaned up as soon as the ThreadLocal object 13 goes out of scope.


Needless to say, I was 12 rather impressed by this implementation, it 11 quite elegantly gets around a lot of concurrency 10 issues (admittedly by taking advantage of 9 being part of core Java, but that's forgivable 8 them since it's such a clever class) and 7 allows for fast and thread-safe access to 6 objects that only need to be accessed by 5 one thread at a time.

tl;dr ThreadLocal's implementation 4 is pretty cool, and much faster/smarter 3 than you might think at first glance.

If 2 you liked this answer you might also appreciate 1 my (less detailed) discussion of ThreadLocalRandom.

Thread/ThreadLocal code snippets taken from Oracle/OpenJDK's implementation of Java 8.

Score: 34

You mean java.lang.ThreadLocal. It's quite simple, really, it's 4 just a Map of name-value pairs stored inside 3 each Thread object (see the Thread.threadLocals field). The API hides 2 that implementation detail, but that's more 1 or less all there is to it.

Score: 9

ThreadLocal variables in Java works by accessing 1 a HashMap held by the Thread.currentThread() instance.

Score: 4

Suppose you're going to implement ThreadLocal, how 38 do you make it thread-specific? Of course 37 the simplest method is to create a non-static 36 field in the Thread class, let's call it 35 threadLocals. Because each thread is represented by 34 a thread instance, so threadLocals in every thread would 33 be different, too. And this is also what 32 Java does:

/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

What is ThreadLocal.ThreadLocalMap here? Because you only 31 have a threadLocals for a thread, so if you simply take 30 threadLocals as your ThreadLocal(say, define threadLocals as Integer), you 29 will only have one ThreadLocal for a specific thread. What 28 if you want multiple ThreadLocal variables for a thread? The 27 simplest way is to make threadLocals a HashMap, the key of each 26 entry is the name of the ThreadLocal variable, and 25 the value of each entry is the value of the ThreadLocal variable. A 24 little confusing? Let's say we have two 23 threads, t1 and t2. they take the same Runnable instance 22 as the parameter of Thread constructor, and they 21 both have two ThreadLocal variables named tlA and tlb. This 20 is what it's like.

t1.tlA

+-----+-------+
| Key | Value |
+-----+-------+
| tlA |     0 |
| tlB |     1 |
+-----+-------+

t2.tlB

+-----+-------+
| Key | Value |
+-----+-------+
| tlA |     2 |
| tlB |     3 |
+-----+-------+

Notice that 19 the values are made up by me.

Now it seems 18 perfect. But what is ThreadLocal.ThreadLocalMap? Why didn't it just 17 use HashMap? To solve the problem, let's see what 16 happens when we set a value through the 15 set(T value) method of the ThreadLocal class:

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

getMap(t) simply returns t.threadLocals. Because 14 t.threadLocals was initilized to null, so we enter createMap(t, value) first:

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

It 13 creates a new ThreadLocalMap instance using the current 12 ThreadLocal instance and the value to be set. Let's 11 see what ThreadLocalMap is like, it's in fact part of 10 the ThreadLocal class

static class ThreadLocalMap {

    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

    ...

    /**
     * Construct a new map initially containing (firstKey, firstValue).
     * ThreadLocalMaps are constructed lazily, so we only create
     * one when we have at least one entry to put in it.
     */
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }

    ...

}

The core part of the ThreadLocalMap class is 9 the Entry class, which extends WeakReference. It ensures that if 8 the current thread exits, it will be garbage 7 collected automatically. This is why it 6 uses ThreadLocalMap instead of a simple HashMap. It passes the 5 current ThreadLocal and its value as the parameter 4 of the Entry class, so when we want to get the 3 value, we could get it from table, which is an 2 instance of the Entry class:

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();
}

This is what is like 1 in the whole picture:

The Whole Picture

More Related questions