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

时间:2009-07-29 19:16:03

标签: java multithreading thread-local thread-static

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

5 个答案:

答案 0 :(得分:101)

这里的所有答案都是正确的,但有点令人失望,因为它们在某种程度上掩盖了ThreadLocal的实施方式。我只是看着source code for ThreadLocal,并对它的实施方式印象深刻。

天真实施

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

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

适合GC的实施

好了再试一次,让我们使用weak references处理垃圾收集问题。处理WeakReferences可能会让人感到困惑,但使用如此构建的地图应该足够了:

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

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

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

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

聪明的实施

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

实现看起来像这样:

ThreadLocal

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

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

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

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

评论建议的确实是此/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; 的{​​{1}}个对象所跟踪的所有值的包私有映射。 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完成,其地图将自动进行GC,并清除所有本地对象。即使保留ThreadLocalThread对象仍由弱引用保留,并且只要Thread对象超出范围就可以清除。


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

tl; dr ThreadLocal的实施非常酷,比您第一眼看到的更快/更智能。

如果您喜欢这个答案,您可能也会感谢我(不太详细)discussion of ThreadLocalRandom

ThreadLocal / ThreadLocal代码摘录自Oracle/OpenJDK's implementation of Java 8

答案 1 :(得分:32)

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

答案 2 :(得分:8)

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

答案 3 :(得分:2)

假设您要实现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 {1}}用于特定线程。如果您想要一个线程的多个ThreadLocal变量怎么办?最简单的方法是将threadLocals设为HashMap,每个条目的keyThreadLocal变量的名称,每个条目的valueThreadLocal变量的值。有点混乱?假设我们有两个主题t1t2。它们采用与Runnable构造函数的参数相同的Thread实例,并且它们都有两个名为ThreadLocaltlA的{​​{1}}个变量。这就是它的样子。

t1.tlA

tlb

t2.tlB

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

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

现在看起来很完美。但是什么是+-----+-------+ | Key | Value | +-----+-------+ | tlA | 2 | | tlB | 3 | +-----+-------+ ?为什么不只使用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)。由于t.threadLocals已初始化为t.threadLocals,因此我们先输入null

createMap(t, value)

它使用当前void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } 实例和要设置的值创建新的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); } ... } 类的核心部分是ThreadLocalMap,它扩展了Entry class。它确保如果当前线程退出,它将被自动垃圾收集。这就是为什么它使用WeakReference而不是简单的ThreadLocalMap。它将当前HashMap及其值作为ThreadLocal类的参数传递,因此当我们想要获取值时,我们可以从Entry获取它,这是一个实例table课程:

Entry

这就是整个画面中的情况:

The Whole Picture

答案 4 :(得分:-1)

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

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

参考:JCIP