Java的ThreadLocal是如何实现的?
ThreadLocal如何实现? 它是用Java实现的(使用从ThreadID到对象的一些并发映射),还是使用一些JVM钩子来更有效地执行?
这里的所有答案都是正确的,但有点令人失望,因为他们略微暗示了ThreadLocal
的实现是多么的聪明。 我只是在看ThreadLocal
的源代码,并且对它的实现感到惊喜。
天真的执行
如果我要求您在javadoc中描述的API实现一个ThreadLocal<T>
类,您会做什么? 初始实现可能是一个使用Thread.currentThread()
作为其键的ConcurrentHashMap<Thread,T>
。 这将工作得相当好,但也有一些缺点。
- 线程争用 –
ConcurrentHashMap
是一个相当聪明的类,但它最终还是要处理以任何方式防止multithreading混淆,如果不同的线程定期碰到它,会有减速。 - 即使在线程完成并且可以被GC化之后,永久地保持指向线程和对象的指针。
GC友好的实施
好吧再试一次,让我们通过使用弱引用来处理垃圾收集问题。 处理WeakReference可能会让人困惑,但使用如下构build的地图应该足够了:
Collections.synchronizedMap(new WeakHashMap<Thread, T>())
或者如果我们使用番石榴 (我们应该!):
new MapMaker().weakKeys().makeMap()
这意味着一旦没有其他人坚持线程(暗示已完成),键/值可以被垃圾收集,这是一个改进,但仍然没有解决线程争用问题,这意味着到目前为止我们的ThreadLocal
并非全部这是一个惊人的类。 而且,如果有人决定在完成之后坚持使用Thread
对象,那么它们就不会被GC化,因此即使它们在技术上无法到达,我们的对象也不会被GC化。
聪明的执行
我们一直在思考ThreadLocal
作为线程到值的映射,但也许这并不是正确的思考方式。 而不是把它看作是从每个ThreadLocal对象中的Threads到values的映射,如果我们把它想象成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;
正如注释所暗示的那样,对于此Thread
, ThreadLocal
对象正在跟踪所有值的包 – 私有映射。 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化,所有本地对象将被清理。 即使Thread
被保持, ThreadLocal
对象被弱引用保存,并且可以在ThreadLocal
对象超出作用域时立即清除。
不用说,这个实现给我留下了深刻的印象,它非常优雅地解决了很多并发问题(不可否认,通过利用成为核心Java的一部分,但是这是可以原谅它们的,因为它是如此聪明的类),并且允许快速线程安全地访问只需要一次一个线程访问的对象。
ThreadLocal
的实现非常酷,比你初看起来更快/更聪明。
如果你喜欢这个答案,你也许会喜欢我的ThreadLocalRandom
(不太详细的) 讨论 。
Thread
/ Thread
ThreadLocal
代码片段取自Oracle / OpenJDK的Java 8实现 。
你的意思是java.lang.ThreadLocal
。 这非常简单,实际上,它只是存储在每个Thread
对象内的名称 – 值对映射(请参阅Thread.threadLocals
字段)。 API隐藏了实现细节,但这或多或less都是它的全部。
Java中的ThreadLocalvariables通过访问由Thread.currentThread()实例持有的HashMap来工作。
假设你要实现ThreadLocal
,你如何使它成为线程特定的? 当然最简单的方法是在Thread类中创build一个非静态字段,我们称之为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
variables呢? 最简单的方法是将threadLocals
为HashMap
,每个条目的key
是ThreadLocal
variables的名称,每个条目的值是ThreadLocal
variables的值。 有点混乱? 假设我们有两个线程, t1
和t2
。 他们采用与Thread
构造函数相同的Runnable
实例,它们都有两个名为tlA
和tlb
ThreadLocal
variables。 这就是它的样子。
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.threadLocals
被t.threadLocals
为null
,所以我们先inputcreateMap(t, value)
:
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
它使用当前的ThreadLocal
实例和要设置的值创build一个新的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 (ie 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
类的parameter passing,所以当我们想要得到这个值的时候,我们可以从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(); }
整个画面是这样的: