ThreadLocal源码阅读
简介
ThreadLocal通过通过线程隔离的方式防止任务在共享资源上产生冲突,即用资源副本保证本线程访问资源安全性。狭义地说就是,每个线程访问的同种资源实际上都是不同的实例。
例子
假设有两个不同的数据库源,并且这俩数据库在业务代码中都得被多线程共享,那么自然会为这两个数据库分别设置一个ThreadLocal,来使得数据库连接在不同够的线程中隔离。
public class DB1 {
public static final ThreadLocal<DB1Connection> tl;
}
public class DB1 {
public static final ThreadLocal<DB2Connection> tl;
}
在线程中获取数据库连接的时候通过ThreadLocal.get获取:
DB1Connection conn1 = db1.tl.get();
DB1Connection conn2 = db2.tl.get();
可以发现Thread与ThreadLocal形成了多对多的关系,如何做到呢,就靠ThreadLocalMap(Thread的成员变量threadLocals
)。这里先把它看成一个键为ThreadLocal值为Object的普通map。线程调用ThreadLocal.get的时候,get方法只需要:
Thread.currentThread().threadLocals.get(this);
就能获取到该Thread在这个ThreadLocal上对应的资源了(在这里是数据库连接)。
代码分析
ThreadLocalMap
基于上面ThreadLocal的简单使用场景,梳理一下下面三个的类之间的关系:
- Thread
- ThreadLocal
- ThreadLocalMap
Thread有一个ThreadLocalMap成员变量,来记录Thread在不同ThreadLocal上所拥有的资源:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap是一个内部实现的一个map,ThreadLocal只用了ThreadLocalMap的如下三个方法:
Entry getEntry(ThreadLocal<?> key)
void set(ThreadLocal<?> key, Object value)
void remove(ThreadLocal<?> key)
因此对于ThreadLocal来说这玩意可以看成一个键类型ThreadLocal值类型为Object的Map。
那么这个map如何被使用呢?由于一个Thread可以使用不同的ThreadLocalMap来获取资源,因此通过使用ThreadLocalMap作为Thread的成员变量,来记录该线程在各个ThreadLocal上所对应的资源。当线程使用ThreadLocal.get获取属于该线程的资源的时候,ThreadLocal就能通过该成员变量获取当前线程(Thread.currentThread()
)在该ThreadLocal(this
)上对应的资源。
Thread.currentThread().threadLocals.get(this);
ThreadLocalMap用数组实现map,用线性探测法解决冲突,并且在复杂因子超过一定量的时候进行压缩甚至扩容。
最重要的成员变量table
数组,数据元素是继承自WeakReference<ThreadLocal<?>>
的Entry:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
使用ThreadLocal弱引用而不是直接用ThreadLocal作为key的考量在于:ThreadLocal可能不会再被任何线程引用,此时就能检测到这个Entry变成了"stale"的,可以拿来复用了。
其他的成员变量size, threshold就没什么好介绍的了。
接下来看看上面说的最重要的三个方法。
getEntry
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.refersTo(key))
return e;
else
return getEntryAfterMiss(key, i, e);
}
既然ThreadLocal作ThreadLocalMap的键,那第一步就是计算它的哈希值...哦原来不用计算,哈希值就是threadLocalHashCode成员变量,至于它是怎么初始化的文后再说,只需要知道它保存了这个ThreadLocal实例的哈希值就行。
然后看一下数组对应位置里面是否是这个key,如果不在的话有两种可能:
- 由于哈希冲突、压缩表、扩容表,导致该键被分配到了别的slot
- 根本不存在该键
因此下面的getEntryAfterMiss会遍历其他的位置找这个key,最终找不到就返回null。
set
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.refersTo(key)) {
e.value = value;
return;
}
if (e.refersTo(null)) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
看着比getEntry长一点,但其实也很简单:使用线性探测法,从哈希的位置开始找到可用的slot,如果是stale slot的话就直接替换。最后稍微压缩一下表(cleanSomeSlots),然后如果负载因子还是太大的话就继续压缩(rehash里调用expungeStaleEntries),负载因子还是还是太大的话,直接扩容成两倍大小(rehash里调用resize)!
remove
上面两个方法能看懂的话,remove更加不用说了,简单!
ThreadLocalMap其他的细节
- Entry.refersTo(obj)方法:检测弱引用是否引用了对象obj,不用get() == obj的原因在于get返回对象的强引用,对GC不友好。如果你只是想检测一下,并不想要返回强引用的话,
refersTo
是最佳选择。 - ThreadLocalMap其他方法比如rehash、expungeStaleEntry等,基本是用于压缩表啥的细枝末节,研究意义不大。
- ThreadLocal的threadLocalHashCode怎么来的,以及ThreadLocal这个魔数怎么来的:What is the meaning of 0x61C88647 constant in ThreadLocal.java
ThreadLocal
ThreadLocal本身没什么可以探究的,稍微翻看一下源码就能理解,巧妙的点在于ThreadLocal为线程资源隔离提供了一个很好的思路。总而言之,可以看成每个ThreadLocal实例对应一种资源,每个线程可以通过ThreadLocal获取该种资源的实例,同一个线程多次获取到的是同一个实例,不同线程获取到的是不同的实例,以实现线程对该种资源的隔离访问。
下面说一下ThreadLocal的一些派生类:
SuppliedThreadLocal
配合ThreadLocal的静态方法withInitial使用,不想用匿名类并重载initialValue的方式创建ThreadLocal的话,可以使用withInitial
TerminatingThreadLocal
这个类作为ThreadLocal的一个扩展,除了基本的ThreadLocal资源隔离,还能让你在线程退出的时候做一些比如资源清理之类的事情。因此用户只需要重载这个方法:
protected void threadTerminated(T value) {}
当线程结束的时候,这个方法就会自动被调用。那么这是如何做到的呢?看看这个方法:
/**
* Invokes the TerminatingThreadLocal's {@link #threadTerminated()} method
* on all instances registered in current thread.
*/
public static void threadTerminated() {
for (TerminatingThreadLocal<?> ttl : REGISTRY.get()) {
ttl._threadTerminated();
}
}
这个方法会在Thread.exit中被调用。其中REGISTRY.get()
获取该线程所有注册的TerminatingThreadLocal,对每个TerminatingThreadLoca将资源取出来作为参数,回调用户注册好的方法:
private void _threadTerminated() { threadTerminated(get()); }
最后,注册会在创建资源的时候发生,remove的时候取消(资源已经被移除了,因此也没必要拿着null值去回调)
InheritableThreadLocal
ThreadLocal很明显只要是不同的线程get拿到的一定是不同的资源,因此InheritableThreadLocal提供了子线程想要拿到父线程的资源的方法。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
public InheritableThreadLocal() {}
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
这个派生类很简单,只重载了这么几个方法,Thread.inheritableThreadLocals成员保存了可用于继承资源的ThreadLocal对应的ThreadLocalMap。
相比ThreadLocal.ThreadLocalMap的懒加载(在get、set时才会为线程创建ThreadLocalMap),InheritableThreadLocal.ThreadLocalMap会在线程构造函数中就被创建,并且当且仅当在线程的构造函数中继承父线程的资源:
private Thread(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// ...
Thread parent = currentThread();
// 在这里继承父线程在ThreadLocal中的资源
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
// ...
}
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
// 注意这里,子线程的ThreadLocalMap使用的数组是新的实例,而不是指向parentMap的数组
// 因此继承只在线程构造的时刻完成,在这之后父子线程对ThreadLocal的操作毫无关系
table = new Entry[len];
for (Entry e : parentTable) {
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
如果用了InheritableThreadLocal,但是某个子线程并不想继承父线程的资源怎么办?Thread的构造函数不是有inheritThreadLocals可以控制这个行为吗hhhhhh...