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

rip*_*234 74 java multithreading thread-static thread-local

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

dim*_*414 108

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

天真的实施

如果我要求你实现一个ThreadLocal<T>给定javadoc中描述的API 的类,你会怎么做?初始实施可能是一个ConcurrentHashMap<Thread,T>使用Thread.currentThread()作为其关键.这将相当不错,但确实有一些缺点.

  • 线程争用 - ConcurrentHashMap是一个相当聪明的类,但它最终仍然必须处理防止多个线程以任何方式与它混合,并且如果不同的线程经常命中它,将会有减速.
  • 永久保持指向Thread和对象的指针,即使在Thread完成后也可以进行GC操作.

GC友好的实施

好了再试一次,让我们使用弱引用处理垃圾收集问题.处理WeakReferences可能会令人困惑,但使用如此构建的映射应该足够了:

 Collections.synchronizedMap(new WeakHashMap<Thread, T>())
Run Code Online (Sandbox Code Playgroud)

或者,如果我们使用番石榴(我们应该!):

new MapMaker().weakKeys().makeMap()
Run Code Online (Sandbox Code Playgroud)

这意味着一旦没有其他人持有线程(暗示它已经完成),键/值可以被垃圾收集,这是一个改进,但仍然没有解决线程争用问题,这意味着到目前为止我们ThreadLocal不是所有的一流的惊人.此外,如果有人决定在Thread完成后抓住物体,他们就永远不会被GC,因此我们的物体也不会,即使它们现在在技术上无法到达.

聪明的实施

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

实现看起来像这样:

// called for each thread, and updated by the ThreadLocal instance
new WeakHashMap<ThreadLocal,T>()
Run Code Online (Sandbox Code Playgroud)

这里没有必要担心并发性,因为只有一个线程会访问这个地图.

Java开发人员在这方面比我们有一个很大的优势 - 他们可以直接开发Thread类并为其添加字段和操作,这正是他们所做的.

java.lang.Thread那里有以下几行:

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
Run Code Online (Sandbox Code Playgroud)

正如评论所暗示的那样,确实是ThreadLocal对象跟踪的所有值的包私有映射Thread.执行ThreadLocalMap不是一个WeakHashMap,但它遵循相同的基本合同,包括通过弱引用来保持其键.

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();
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

基本上,使用此线程中的映射来保存所有ThreadLocal对象.这样,我们就不必担心其他Threads中的值(ThreadLocal字面上只能访问当前Thread中的值),因此没有并发问题.此外,一旦Thread完成,其地图将自动进行GC,并清除所有本地对象.即使Thread被保持ThreadLocal住,对象也被弱引用保持,并且一旦ThreadLocal对象超出范围就可以清除.


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

tl;博士 ThreadLocal的实施非常酷,比你初看起来的速度更快/更聪明.

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

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


ska*_*man 32

你的意思java.lang.ThreadLocal.它非常简单,实际上,它只是存储在每个Thread对象中的名称 - 值对的映射(参见Thread.threadLocals字段).API隐藏了实现细节,但这或多或少都与它有关.

  • 正确,ThreadLocalMap周围或内部没有同步或锁定,因为它只能在线程中访问. (7认同)

Chr*_*est 8

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

  • 我基本上就是这么说的。currentThread() 返回一个 Thread 实例,其中包含 ThreadLocals 到值的映射。 (2认同)