这里的所有答案都是正确的,但有点令人失望,因为它们在某种程度上掩盖了如何聪明ThreadLocal
的实现是.我只是在看源代码ThreadLocal http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/tip/src/share/classes/java/lang/ThreadLocal.java并对它的实施方式印象深刻。
幼稚的实施
如果我要求你实施一个ThreadLocal<T>
给定 javadoc 中描述的 API,您会做什么?初步实施可能是ConcurrentHashMap<Thread,T>
using Thread.currentThread()
作为它的关键。这将工作得相当好,但也有一些缺点。
- 线程争用 -
ConcurrentHashMap
是一个非常聪明的类,但它最终仍然必须处理防止多个线程以任何方式破坏它,并且如果不同的线程定期命中它,就会出现速度减慢的问题。
- 永久保留指向线程和对象的指针,即使线程已完成并且可以被 GC 后也是如此。
GC 友好的实现
好的,再试一次,让我们使用以下方法来处理垃圾收集问题弱引用 http://en.wikipedia.org/wiki/Weak_reference。处理 WeakReferences 可能会令人困惑,但使用如下构建的映射应该足够了:
Collections.synchronizedMap(new WeakHashMap<Thread, T>())
或者如果我们使用Guava http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/MapMaker.html(我们应该如此!):
new MapMaker().weakKeys().makeMap()
这意味着一旦没有其他人持有该线程(意味着它已完成),键/值就可以被垃圾收集,这是一种改进,但仍然没有解决线程争用问题,这意味着到目前为止我们的ThreadLocal
类不是那么令人惊奇。此外,如果有人决定坚持下去Thread
对象完成后,它们永远不会被GC,因此我们的对象也不会被GC,即使它们现在在技术上是无法访问的。
巧妙的实施
我们一直在思考ThreadLocal
作为线程到值的映射,但这实际上可能不是正确的思考方式。与其将其视为从 Threads 到每个 ThreadLocal 对象中的值的映射,不如将其视为 ThreadLocal 对象到值的映射会怎样?在每个线程中?如果每个线程都存储映射,并且 ThreadLocal 只是为该映射提供一个很好的接口,那么我们就可以避免以前实现的所有问题。
一个实现看起来像这样:
// called for each thread, and updated by the ThreadLocal instance
new WeakHashMap<ThreadLocal,T>()
这里无需担心并发性,因为只有一个线程会访问该映射。
Java 开发人员比我们有一个主要优势 - 他们可以直接开发 Thread 类并向其添加字段和操作,而这正是他们所做的。
In java.lang.Thread http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/tip/src/share/classes/java/lang/Thread.java有以下几行:
/* 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();
}
And 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 的一部分的优势,但这是可以原谅的,因为它是一个如此聪明的类),并且允许快速和对一次只需要一个线程访问的对象进行线程安全访问。
tl;dr ThreadLocal
的实现非常酷,并且比您乍一看更快/更智能。
如果您喜欢这个答案,您可能还会欣赏我的(不太详细)的讨论ThreadLocalRandom https://stackoverflow.com/a/41760223/113632.
Thread
/ThreadLocal
code snippets taken from Oracle/OpenJDK's implementation of Java 8 http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/jdk8-b132.