How is Java's ThreadLocal implemented under the hood?
Asked Answered
R

5

89

How is ThreadLocal implemented? Is it implemented in Java (using some concurrent map from ThreadID to object), or does it use some JVM hook to do it more efficiently?

Renaldo answered 29/7, 2009 at 19:16 Comment(0)
G
136

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

The Naive Implementation

If I asked you to implement a ThreadLocal<T> class given the API described in the javadoc, what would you do? An initial implementation would likely be a ConcurrentHashMap<Thread,T> using Thread.currentThread() as its key. This will would work reasonably well but does have 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 with the garbage collection issue by using weak references. Dealing with WeakReferences can be confusing, but it should be sufficient to use a map built like so:

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

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

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

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

The Clever Implementation

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

An implementation would look something like this:

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

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

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

In 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 comment suggests is indeed a package-private mapping of all values being tracked by ThreadLocal objects for this Thread. The implementation of ThreadLocalMap is not a WeakHashMap, but it follows the same basic contract, including holding its keys by weak reference.

ThreadLocal.get() is then 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 a map in this Thread to hold all our ThreadLocal objects. This way, we never need to worry about the values in other Threads (ThreadLocal literally can only access the values in the current Thread) and therefore have no concurrency issues. Furthermore, once the Thread is done, its map will automatically be GC'ed and all the local objects will be cleaned up. Even if the Thread is held onto, the ThreadLocal objects are held by weak reference, and can be cleaned up as soon as the ThreadLocal object goes out of scope.


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

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

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

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

Gully answered 27/3, 2013 at 6:25 Comment(16)
Your answers looks awesome but it's too long for me right now. +1 and accepted, and I'm adding it to my getpocket.com account to read it later. Thanks!Renaldo
I need a ThreadLocal-like thing that also let's me access the full list of values, much like map.values(). So, my naive implementation is a WeakHashMap<String, Object> where the key is Thread.currentThread().getName(). This avoids the reference to the Thread itself. If the thread goes away, then nothing is holding the Thread's name anymore (an assumption, I admit) and my value will go away.Cithara
I actually answered a question to that effect quite recently. A WeakHashMap<String,T> introduces several problems, it's not threadsafe, and it "is intended primarily for use with key objects whose equals methods test for object identity using the == operator" - so actually using the Thread object as a key would do better. I would suggest using the Guava weakKeys map I describe above for your use case.Gully
I ended up using regular HashMap and manually synchronized access to it. The Map is supposed to hold a bunch of connections to APNS. If the context is destroyed, I want an opportunity to gracefully close those connections. In the meantime, I want to reuse the connection if I've already made it. Despite your warning to never use the other solution, I think my situation warrants it. Thanks for your help.Cithara
Well the weak keys aren't necessary, but consider using a ConcurrentHashMap over a synchronized HashMap - the former is designed for multi-threaded access and will work much better in the case where each thread is generally accessing a different key.Gully
Out of curiosity, does ThreadLocal have any fields, or is it simply used as an identity token?Nagoya
Just one, private final int threadLocalHashCode, used to lookup the ThreadLocal in the Thread map in a collision-avoiding way. Other than this, ThreadLocal itself has no state. Note you can see the source to ThreadLocal.java in your own install of Java, look for a src.zip file.Gully
@dimo414: Thanks for an informative answer! Did you understand how the "stale" entries (the term used in the source to refer to entries where the ThreadLocal object is GC:ed) are used? Specifically, exactly when are they removed? Could you write a few words about that?Entomologize
@Entomologize - great question. I'll try to revisit my answer in more detail at some point, but in essence it looks to be some additional garbage collection to clean up the case where Threads are long-lived but ThreadLocals are regularly falling out of scope.Gully
Excellent explanation. Any idea why they went with a custom implementation instead of WeakHashMap? Also, wouldn't it make sense to have a cleanup process for threads that stay in scope but are no longer runnable?Violetteviolin
@Violetteviolin thanks, glad it was informative! I believe it's because ThreadLocal's requirements are subtly different from a WeakHashMap. For instance it uses a custom hashing function "* that eliminates collisions in the common case where consecutively constructed ThreadLocals are used by the same threads*", and provides different cleanup semantics.Gully
I'm conjecturing, but my guess is that ThreadLocal tries to do less cleanup because time spent cleaning up stale values is wasteful, and for ThreadLocal's use case it's safe to do less of it than WeakHashMap requires.Gully
@Gully t.threadLocal = null; seems like a pretty cheap cleanup to me.Violetteviolin
@Violetteviolin this class is highly tuned, so I would start from the assumption that such considerations have been made. A quick glance shows that when a thread terminates normally Thread.exit() is called and you'll see threadLocals = null; right there. A comment references this bug which you might also enjoy reading.Gully
Isn't that ThreadLocal<T> concept similar to what we have in Spring as Scope and Lookup ... i.e. T class is annotated with something like @Scope("Thread") and the method providing thread local object is annotated with Lookup ?Leyes
I haven't used Spring, but most languages have a thread-local mechanism of some sort.Gully
I
34

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

Iron answered 29/7, 2009 at 19:20 Comment(2)
I can't see why any should be needed, given that by definition the data is only visible to a single thread.Iron
Correct, there's no synchronisation or locking around or within the ThreadLocalMap, as it's only accessed in-thread.Anastatius
R
9

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

Rosenzweig answered 29/7, 2009 at 19:20 Comment(2)
That's not correct (or at least it isn't any longer). Thread.currentThread() is a native call in the Thread.class. Also Thread has a "ThreadLocalMap" which is a single-bucket (array) of hash codes. This object doesn't support the Map interface.Tachycardia
That's basically what I said. currentThread() returns a Thread instance, which holds a map of ThreadLocals to values.Rosenzweig
M
4

Suppose you're going to implement ThreadLocal, how do you make it thread-specific? Of course the simplest method is to create a non-static field in the Thread class, let's call it threadLocals. Because each thread is represented by a thread instance, so threadLocals in every thread would be different, too. And this is also what 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 have a threadLocals for a thread, so if you simply take threadLocals as your ThreadLocal(say, define threadLocals as Integer), you will only have one ThreadLocal for a specific thread. What if you want multiple ThreadLocal variables for a thread? The simplest way is to make threadLocals a HashMap, the key of each entry is the name of the ThreadLocal variable, and the value of each entry is the value of the ThreadLocal variable. A little confusing? Let's say we have two threads, t1 and t2. they take the same Runnable instance as the parameter of Thread constructor, and they both have two ThreadLocal variables named tlA and tlb. This is what it's like.

t1.tlA

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

t2.tlB

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

Notice that the values are made up by me.

Now it seems perfect. But what is ThreadLocal.ThreadLocalMap? Why didn't it just use HashMap? To solve the problem, let's see what happens when we set a value through the 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 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 creates a new ThreadLocalMap instance using the current ThreadLocal instance and the value to be set. Let's see what ThreadLocalMap is like, it's in fact part of 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 the Entry class, which extends WeakReference. It ensures that if the current thread exits, it will be garbage collected automatically. This is why it uses ThreadLocalMap instead of a simple HashMap. It passes the current ThreadLocal and its value as the parameter of the Entry class, so when we want to get the value, we could get it from table, which is an 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 in the whole picture:

The Whole Picture

Mcminn answered 12/11, 2015 at 16:23 Comment(0)
G
-2

Conceptually, you can think of a ThreadLocal<T> as holding a Map<Thread,T> that stores the thread-specific values, though this is not how it is actually implemented.

The thread-specific values are stored in the Thread object itself; when the thread terminates, the thread-specific values can be garbage collected.

Reference : JCIP

Gridley answered 29/11, 2017 at 21:41 Comment(1)
Conceptally, yes. But as you see form other answers above the implementation is entirely different.Tondatone

© 2022 - 2024 — McMap. All rights reserved.