繁体   English   中英

Java 的 ThreadLocal 是如何在底层实现的?

[英]How is Java's ThreadLocal implemented under the hood?

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

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

天真的实现

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

  • 线程争用 - ConcurrentHashMap是一个非常聪明的类,但它最终仍然需要处理防止多个线程以任何方式使用它,如果不同的线程定期访问它,则会出现减速。
  • 永久保持一个指向线程和对象的指针,即使在线程完成并且可以被 GC 处理之后。

GC 友好的实现

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

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

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

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

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

聪明的实施

我们一直将ThreadLocal视为线程到值的映射,但也许这实际上并不是正确的思考方式。 与其将其视为从 Threads 到每个 ThreadLocal 对象中的值的映射,不如将其视为 ThreadLocal 对象到每个 Thread 中的值的映射呢? 如果每个线程都存储映射,而 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;

正如评论所暗示的那样,这确实是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(); }

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

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


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

tl;dr ThreadLocal的实现非常酷,而且比乍一看可能更快/更智能。

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

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

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

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

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

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

这里的ThreadLocal.ThreadLocalMap是什么? 因为您只有一个线程的threadLocals ,所以如果您简单地将threadLocals作为您的ThreadLocal (例如,将 threadLocals 定义为Integer ),那么您将只有一个特定线程的ThreadLocal 如果你想要一个线程有多个ThreadLocal变量怎么办? 最简单的方法就是让threadLocals一个HashMap ,在key的每个条目的是名ThreadLocal变量, value中的每个条目的是价值ThreadLocal变量。 有点混乱? 假设我们有两个线程, t1t2 它们采用相同的Runnable实例作为Thread构造函数的参数,并且它们都有两个名为tlAtlb ThreadLocal变量。 这就是它的样子。

t1.tlA

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

t2.tlB

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

请注意,这些值是由我组成的。

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

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)只返回t.threadLocals 因为t.threadLocalst.threadLocalsnull ,所以我们先输入createMap(t, value)

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

它使用当前的ThreadLocal实例和要设置的值创建一个新的ThreadLocalMap实例。 让我们看看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);
    }

    ...

}

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

整个画面是这样的:

全貌

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

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

参考: JCIP

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM