Java 的 ThreadLocal 是如何在幕后实现的?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1202444/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 15:36:25  来源:igfitidea点击:

How is Java's ThreadLocal implemented under the hood?

javamultithreadingthread-localthread-static

提问by ripper234

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?

ThreadLocal 是如何实现的?它是在 Java 中实现的(使用一些从 ThreadID 到对象的并发映射),还是使用一些 JVM 钩子来更有效地完成它?

回答by dimo414

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 ThreadLocaland was pleasantly impressed by how it's implemented.

这里的所有答案都是正确的,但有点令人失望,因为它们在某种程度上掩盖了ThreadLocal的实现有多聪明。我只是在查看源代码,ThreadLocal并对其实现方式印象深刻。

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.

如果我让你ThreadLocal<T>根据 javadoc 中描述的 API实现一个类,你会怎么做?最初的实现可能是将ConcurrentHashMap<Thread,T>usingThread.currentThread()作为其关键。这将工作得相当好,但确实有一些缺点。

  • Thread contention - ConcurrentHashMapis 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.
  • 线程争用 -ConcurrentHashMap是一个非常聪明的类,但它最终仍然需要处理防止多个线程以任何方式破坏它,如果不同的线程经常碰到它,就会出现减速。
  • 永久保持一个指向线程和对象的指针,即使在线程完成并且可以被 GC 处理之后。

The GC-friendly Implementation

GC 友好的实现

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:

好的再试一次,让我们通过使用弱引用来处理垃圾收集问题。处理 WeakReferences 可能会令人困惑,但使用像这样构建的地图应该就足够了:

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

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

或者,如果我们使用Guava(我们应该使用!):

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 ThreadLocalisn't all that amazing of a class. Furthermore, if someone decided to hold onto Threadobjects after they'd finished, they'd never be GC'ed, and therefore neither would our objects, even though they're technically unreachable now.

这意味着一旦没有其他人持有线程(暗示它已完成),键/值可以被垃圾收集,这是一个改进,但仍然没有解决线程争用问题,这意味着到目前为止我们ThreadLocal还不是全部一个班级的惊人。此外,如果有人决定在Thread对象完成后保留它们,它们将永远不会被 GC,因此我们的对象也不会,即使它们现在在技术上无法访问。

The Clever Implementation

巧妙的实施

We've been thinking about ThreadLocalas 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.

我们一直在考虑ThreadLocal线程到值的映射,但也许这实际上并不是正确的思考方式。与其将其视为从 Threads 到每个 ThreadLocal 对象中的值的映射,不如将其视为 ThreadLocal 对象到每个 Thread 中的值的映射呢?如果每个线程都存储映射,而 ThreadLocal 只是为该映射提供了一个很好的接口,我们就可以避免之前实现的所有问题。

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.

Java 开发人员在这里比我们有一个主要优势——他们可以直接开发 Thread 类并向其添加字段和操作,而这正是他们所做的。

In java.lang.Threadthere's the following lines:

java.lang.Thread有下面几行:

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/* 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 ThreadLocalobjects for this Thread. The implementation of ThreadLocalMapis not a WeakHashMap, but it follows the same basic contract, including holding its keys by weak reference.

正如评论所暗示的那样,这确实是ThreadLocal对象为此跟踪的所有值的包私有映射Thread。的实现ThreadLocalMap不是 a WeakHashMap,但它遵循相同的基本契约,包括通过弱引用保持其密钥。

ThreadLocal.get()is then implemented like so:

ThreadLocal.get()然后像这样实现:

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

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

本质上,在这个线程中使用一个映射来保存我们所有的ThreadLocal对象。这样,我们永远不需要担心其他线程中的值(ThreadLocal字面上只能访问当前线程中的值),因此没有并发问题。此外,一旦Thread完成,它的映射将自动被 GC 处理,并且所有本地对象都将被清理。即使Thread保持不变,ThreadLocal对象也由弱引用保持,并且可以在ThreadLocal对象超出范围时立即清除。



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.

毋庸置疑,这个实现给我留下了深刻的印象,它非常优雅地解决了许多并发问题(诚然通过利用作为核心 Java 的一部分,但这是可以原谅的,因为它是一个如此聪明的类)并允许快速和对一次只需要由一个线程访问的对象的线程安全访问。

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

tl;drThreadLocal的实现非常酷,而且比乍一看可能更快/更聪明。

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

如果您喜欢这个答案,您可能还会欣赏我(不太详细)ThreadLocalRandom.

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

Thread/ThreadLocal取自Oracle/OpenJDK 的 Java 8 实现的代码片段。

回答by skaffman

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

你的意思是java.lang.ThreadLocal。它非常简单,实际上,它只是存储在每个Thread对象中的名称-值对的 Map (请参阅该Thread.threadLocals字段)。API 隐藏了该实现细节,但这或多或少就是它的全部内容。

回答by Chris Vest

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

Java 中的 ThreadLocal 变量通过访问 Thread.currentThread() 实例持有的 HashMap 来工作。

回答by Searene

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 threadLocalsin every thread would be different, too. And this is also what Java does:

假设您要实现ThreadLocal,您如何使其特定于线程?当然最简单的方法是在Thread类中创建一个非静态字段,我们就叫它threadLocals。因为每个线程都由一个线程实例表示,所以threadLocals在每个线程中也会有所不同。这也是 Java 所做的:

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

What is ThreadLocal.ThreadLocalMaphere? Because you only have a threadLocalsfor a thread, so if you simply take threadLocalsas your ThreadLocal(say, define threadLocals as Integer), you will only have one ThreadLocalfor a specific thread. What if you want multiple ThreadLocalvariables for a thread? The simplest way is to make threadLocalsa HashMap, the keyof each entry is the name of the ThreadLocalvariable, and the valueof each entry is the value of the ThreadLocalvariable. A little confusing? Let's say we have two threads, t1and t2. they take the same Runnableinstance as the parameter of Threadconstructor, and they both have two ThreadLocalvariables named tlAand tlb. This is what it's like.

什么是ThreadLocal.ThreadLocalMap在这里吗?因为您只有一个threadLocalsfor 线程,所以如果您只是将其threadLocals作为您的ThreadLocal(例如,将 threadLocals 定义为Integer),那么您将只有一个ThreadLocal用于特定线程。如果你想要ThreadLocal一个线程有多个变量怎么办?最简单的方法是做threadLocals一个HashMapkey每个条目的 是ThreadLocal变量的名称value,每个条目的 是ThreadLocal变量的值。有点混乱?假设我们有两个线程,t1并且t2. 它们采用与构造函数Runnable的参数相同的实例Thread,并且它们都有两个ThreadLocal名为tlA和 的变量tlb。这就是它的样子。

t1.tlA

t1.tlA

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

t2.tlB

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 ThreadLocalclass:

现在看起来很完美。但什么是ThreadLocal.ThreadLocalMap?为什么不直接使用HashMap?为了解决这个问题,让我们看看当我们通过类的set(T value)方法设置一个值时会发生什么ThreadLocal

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.threadLocalswas initilized to null, so we enter createMap(t, value)first:

getMap(t)简单地返回t.threadLocals。因为t.threadLocals被初始化为null,所以我们createMap(t, value)先输入:

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

It creates a new ThreadLocalMapinstance using the current ThreadLocalinstance and the value to be set. Let's see what ThreadLocalMapis like, it's in fact part of the ThreadLocalclass

ThreadLocalMap使用当前ThreadLocal实例和要设置的值创建一个新实例。让我们看看是什么ThreadLocalMap样的,它实际上是ThreadLocal类的一部分

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 ThreadLocalMapclass 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 ThreadLocalMapinstead of a simple HashMap. It passes the current ThreadLocaland its value as the parameter of the Entryclass, so when we want to get the value, we could get it from table, which is an instance of the Entryclass:

ThreadLocalMap该类的核心部分是Entry class,它扩展了WeakReference. 它确保如果当前线程退出,它将被自动垃圾收集。这就是为什么它使用ThreadLocalMap而不是简单的HashMap. 它将当前ThreadLocal及其值作为Entry类的参数传递,因此当我们想要获取值时,我们可以从 中获取它table,它是Entry类的一个实例:

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

The Whole Picture

回答by bpjoshi

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

从概念上讲,您可以将 aThreadLocal<T>视为Map<Thread,T>存储线程特定值的 a,尽管这不是它实际实现的方式。

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

线程特定的值存储在 Thread 对象本身中;当线程终止时,线程特定的值可以被垃圾收集。

Reference : JCIP

参考:JCIP