Java的ThreadLocal是如何在引擎盖下实现的?

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


答案 1

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

天真的实施

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

  • 线程争用 - 是一个非常聪明的类,但它最终仍然必须处理防止多个线程以任何方式与它混淆,如果不同的线程定期击中它,就会有减速。ConcurrentHashMap
  • 永久保留指向线程和对象的指针,即使在线程完成并且可以进行 GC'ed 之后也是如此。

GC 友好型实施

好的,再试一次,让我们使用弱引用来处理垃圾回收问题。处理弱引用可能会令人困惑,但是使用这样构建的映射应该就足够了:

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

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

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

这意味着一旦没有其他人坚持线程(暗示它已完成),键/值可以被垃圾回收,这是一个改进,但仍然没有解决线程争用问题,这意味着到目前为止,我们的类并不是那么神奇。此外,如果有人决定在完成对象后抓住它们,它们永远不会被GC'ed,因此我们的对象也不会,即使它们现在在技术上无法访问。ThreadLocalThread

巧妙的实现

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

实现将如下所示:

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

这里无需担心并发性,因为只有一个线程会访问此映射。

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

java.lang.Thread中,有以下几行:

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

正如注释所暗示的那样,这确实是对象为此跟踪的所有值的包私有映射。的实现不是 ,但它遵循相同的基本协定,包括通过弱引用来握住其键。ThreadLocalThreadThreadLocalMapWeakHashMap

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

就像这样: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;
}

从本质上讲,在此线程中使用映射来保存我们所有的对象。这样,我们就不需要担心其他线程中的值(字面上只能访问当前线程中的值),因此没有并发问题。此外,完成后,其地图将自动进行GC'ed,并且所有本地对象都将被清理。即使 将 保持,对象也会被弱引用保留,并且可以在对象超出范围后立即清理。ThreadLocalThreadLocalThreadThreadThreadLocalThreadLocal


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

tl;博士的实现非常酷,而且比你乍一看想象的要快/聪明得多。ThreadLocal

如果你喜欢这个答案,你也可能喜欢我对ThreadLocalRandom(不太详细的)讨论。

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


答案 2

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