[toc]
前言
介绍InheritableThreadLocal之前,先列举有关Thread的关键点:
- 每一个Thread线程都有属于自己的ThreadLocalMap,里面的节点属于弱引用的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中get值的时候,首先通过Thread.currentThread得到当前线程,然后拿到这个线程的ThreadLocalMap,通过当前ThreadLocal对象取得Entry中的value。
- set值的时候同理,更改当前线程的ThreadLocalMap中的Entry为当前ThreadLocal对象的value值。
ThreadLocal的BUG
如果子线程想要拿到父线程的ThreadLocal的值怎么办,比如有以下代码,由于ThreadLocal的实现机制,在子线程get时,我们拿到是当前Thread对象的子线程的对象,那么他的ThreadLocalMap为null,那么我们得到的value也是null。
final ThreadLocal threadLocal=new ThreadLocal(){
//给threadLocal设置一个初始值
@Override
protected Object initialValue(){
return "ceshi";
}
};
new Thread(new Runnable(){
@Override
public void run(){
threadLocal.get();//null
}
}).start();
所以子线程是拿不到父线程的ThreadLocalMap的值,现在就需要InheeritableThreadLocal了。
InheritableThreadLocal的实现
很多时候我们有子线程获取父线程的ThreadLocal的需求,要如何解决,这就是InheritableThreadLocal所做的事情,先看他的实现:
public class InheritableThreadLocal<T> extends ThreadLocal<T>{
/**
* 计算此可继承线程本地的子线程的初始值,
* 变量作为父级值,传入方法,进行处理
* 默认返回父线程的值,如果需要处理,
* 重写该方法
*/
protected T childValue(T parentValue){
return parentValue;
}
/**
* 重写ThreadLocal类中的getMap方法,在原来ThreadLocal中返回t.threadLocals,
* 而现在返回inheritableThreadLocals,因为Thread类中也有一个要保存父子传递的变量
*/
ThreadLocalMap getMap(Thread t){
return t.inheritableThreadLocals;
}
/**
* 在创建ThreadLocalMap的时候不给t.threadLocal赋值,
* 而是给inheritableThreadLocals变量赋值
*/
void createMap(Thread t,T firstValue){
t.inheritableThreadLocals=new ThreadLocalMap(this,firstValue);
}
}
如果使用InheritableThreadLocal,那么保存所有的东西已经不是在原来的t.threadLocals里面,而是一个新的t.inheritableThreadLocals变量中,下面是Thread类中的两个变量的定义。
//与此线程相关的ThreadLocal值。这map是由ThreadLocal类维护。
ThreadLocal.ThreadLocalMap threadLocals=null;
//与此线程相关的可继承线程本地值。这map是由InheritableThreadLocal类维护。
ThreadLocal.ThreadLocalMap inheritableThreadLocals=null;
InheritableThreadLocal如何实现拿到父线程的值
使用一个常见的想法就是把父线程的所有值都copy到子线程中。
下面看一下new Thread的部分代码
//Thread init的部分源码
/**
* 初始化线程
* @param g 线程组
* @param target 调用run()方法的对象
* @param name the 新线程的名称
* @param stackSize 新线程的所需堆栈大小,或 0表示将忽略此参数。
* @param acc 要继承的AccessControlContext;如果为null,则为 AccessController.getContext()
* @param inheritThreadLocals 如果{@code true},则从构造线程继承可继承线程局部变量的初始值
* inheritThreadLocals这个参数一般都是true,只有系统调用传参时候才有可能是false
*/
private void init(ThreadGroup g,Runnable target,String name,
long stackSize,AccessControlContext acc,
boolean inheritThreadLocals){
//省略上面代码
if (inheritThreadLocals && parent.inheritableThreadLocals != null){
//copy父线程的map,创建一个新的map赋值给当前线程的inherableThreadLocals
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
//省略下面代码
}
而拷贝属于浅拷贝,key和value都是原来的引用地址
//ThreadLocal的部分源码
/**
* 创建继承的线程局部变量映射的工厂方法。
* 设计为仅从Thread构造函数调用
* @param parentMap 与父线程关联的map
* @return 包含父级可继承绑定的映射
*/
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
/**
* 构造一个新的map,其中包括给定父map中的所有InheritableThreadLocals
* 仅由createInheritedMap调用。
* @param parentMap 与父线程关联的map
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
//获取父map的Entry数组
Entry[] parentTable = parentMap.table;
//获取数组长度
int len = parentTable.length;
//设置调整大小阈值以保持最坏的2/3负载系数。大于这个数会进行扩容
setThreshold(len);
//创建一个新的Entry数组,table.length必须始终为2的幂。
table = new Entry[len];
for (int j = 0; j < len; j++) {
//获取父数组的元素
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
//Entry的获取key
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
//获取value,调用childValue可以对父级value进行处理,
//默认直接返回value,需要额外处理,就重写
Object value = key.childValue(e.value);
//创建一个新的Entry,key和value还是使用原来的引用地址
Entry c = new Entry(key, value);
//计算hash
int h = key.threadLocalHashCode & (len - 1);
//对应hash上有元素,就一直循环知道找到非空的位置(解决冲突的方法就是线性探查法)
while (table[h] != null){
//下一个索引位置
h = nextIndex(h, len);
}
//赋值
table[h] = c;
//大小加1
size++;
}
}
}
/**
* 增量和模数len。
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
}
总结
InheritableThreadLocal为什么能解决父子线程传递ThreadLocal值的问题:
- 在创建InheritableThreadLocal对象的时候赋值非线程的t.inheritableThreadLocals变量
- 在创建新的线程时候会检查父线程中t.inheritableThreadLocals变量是否为null,如果不为null,则copy一份ThreadLocalMap到子线程的t.inheritableThreadLocals成员变量中
- 因为InheritableThreadLocal重写了getMap(Thread t)和createMap(Thread t, T firstValue)方法,所以get值的时候,就会从t.inheritableThreadLocals中拿到map对象,从而实现了可以拿到父线程ThreadLocal中的值
网友评论