InheritableThreadLocal父子线程共享变量
前言
学习InheritableThreadLocal之前,需要对ThreadLocal有一定了解,回顾:ThreadLocal 内部实现、应用场景和内存泄漏。ThreadLocalMap是在每个线程之间独有的,不能在其他线程中共享变量。InheritableThreadLocal为解决此问题而生,让我们开始一场学习风暴吧~~~
正文开始
InheritableThreadLocal的源码非常简单,继承自ThreadLocal,重写其中三个方法。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
public InheritableThreadLocal() {
}
protected T childValue(T var1) {
return var1;
}
ThreadLocalMap getMap(Thread var1) {
return var1.inheritableThreadLocals;
}
void createMap(Thread var1, T var2) {
var1.inheritableThreadLocals = new ThreadLocalMap(this, var2);
}
}
getMap返回的是inheritableThreadLocals,跟进Thread内的inheritableThreadLocals变量。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
跟进似乎中断了,InheritableThreadLocal获取的也是ThreadLocalMap ,不过是变量换个名而已,那他是如何做到线程之间共享变量呢?让我们一步步揭开他的神秘面纱~~~
我们仔细想想,InheritableThreadLocal本身并没做什么操作,唯一的可能就是Thread里做了手脚。目前的需求是要求将当前线程里的ThreadLocalMap共享到新开的线程,那么,因为不知道用户何时使用这个数据,所以新开的线程创建好后就必须能访问到这些数据。
没错,你猜对了(终于被我带进坑里了~~)
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
/**
* Initializes a Thread with the current AccessControlContext.
* @see #init(ThreadGroup,Runnable,String,long,AccessControlContext,boolean)
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
//..........
Thread parent = currentThread();
//..........
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
如果当前线程的inheritableThreadLocals != null,新线程:this.inheritableThreadLocals=ThreadLocal.createInheritedMap(parent.inheritableThreadLocals)
传入当前线程的inheritableThreadLocals 。
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
//InheritableThreadLocal重新了这个方法 返回原值
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
//计算hash位置,与ThreadLocal的set方法一样
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
千呼万唤始出来啊~~原理很简单,每当新开个线程,创建新的ThreadLocalMap与Entry,遍历原线程Entry[],直接塞到新entry里(浅拷贝),key为原ThreadLocal对象(ThreadLocal需要全局唯一),value为原值。
收尾
- 由于实际使用线程池,线程可能重复使用,使用时需要注意,可能达不到预期的效果。
- 阿里有个开源项目TransmittableThreadLocal,就是为解决线程池内传递变量(有空再继续学)。