InheritableThreadLocal相比于ThreadLocal,支持子线程继承父线程的数据,一起来看看它的实现原理吧!
1 从ThreadLocal说起
在前面ThreadLocal文章中:ThreadLocal源码深度解析与应用案例。我们知道每个线程都具有一个threadLocals属性,用于存放线程本地变量。
当然如果我们眼睛够细,在我们threadLocals属性下面还可以发现一个同类型的属性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;
实际上,该属性用于实现线程本地变量的继承性:子线程创建时可以从父线程继承线程本地变量。inheritableThreadLocals主要存储可自动向子线程中传递的ThreadLocalMap,他也对应着一个类:InheritableThreadLocal。
在hreadLocal的文章的第一个案例的中,同一个ThreadLocal 变量在父线程(main)中被设置值后, 在子线程(child)中是获取不到的。这应该是正常现象,因为在子线程thread 里面调用get 方法时当前线程为thread 线程,而这里调用set方法设置线程变量的是main 线程,两者是不同的线程,自然子线程访问时返回null。
但是可能有些业务,需要子线程获取到父线程的数据,那么有没有办法让子线程能访问到父线程中的值? 答案是有,那就是InheritableThreadLocal。
2 InheritableThreadLocal的原理
2.1 InheritableThreadLocal的源码
public class InheritableThreadLocal< T >
extends ThreadLocal< T >
InheritableThreadLocal继承自ThreadLocal , 保存线程本地变量的集合也是使用的ThreadLocalMap。但是其增加了一个特性,就是让子线程可以访问在父线程中设置的本地变量。
InheritableThreadLocal类的源码很少,重写了ThreadLocal的3个函数,也是该类的源全部源码:
/**
* 1
* InheritableThreadLocal重写的实现,当调用get/set 方法获取当前线程内部的map 变量时,获取
* 的是inheritableThreadLocals 而不再是threadLocals。
*/
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
/**
* ThreadLocal的实现
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* 操作InheritableThreadLocal对象时,第一次调用set/get方法时
* 创建的是当前线程的inheritableThreadLocals 变量的实例而不再是threadLocals变量实例。
*/
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
/**
* ThreadLocal的实现
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
/**
* 3
* 该函数在父线程中创建子线程并向子线程复制inheritableThreadLocal变量时使用。
* 在ThreadLocal中实现为抛出UnsupportedOperationException异常
*/
protected T childValue(T parentValue) {
return parentValue;
}
/**
* ThreadLocal的实现
*/
T childValue(T parentValue) {
throw new UnsupportedOperationException();
}
2.2 传递数据的原理
传递数据的原理还要从创建线程开始说起!实际上子线程的很多属性如果自己没有指定的话,都会继承父线程的属性,比如线程组、守护/用户线程属性、线程的上下文对象等。
/**
* Thread类的构造器
*
* @param target 线程任务
* @param name 线程名字
*/
public Thread(Runnable target, String name) {
//内部调用init方法
init(null, target, name, 0);
}
/**
* Thread类的init方法,用于初始化线程的参数
*
* @param g 线程组
* @param target 线程任务
* @param name 线程名字
* @param stackSize 当前线程栈大小
*/
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
//调用另外一个init方法
init(g, target, name, stackSize, null, true);
}
/**
* Thread类的另一个init方法
*
* @param g 线程组
* @param target 线程任务
* @param name 线程名字
* @param stackSize 当前线程栈大小
* @param acc 访问控制权限
* @param inheritThreadLocals 如果为true,则从构造线程(父线程)中继承线程局部变量的初始值
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
//设置名字
this.name = name;
//获取当前线程,即父线程(main)
Thread parent = currentThread();
//如果inheritThreadLocals为true并且父线程的inheritableThreadLocals 变量不为null
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
//4 则将父线程inheritableThreadLocals的数据传递至子线程的inheritableThreadLocals属性中
//注意这里时传递的引用值,对于引用类型的值(除了string)相当于浅拷贝,实际上他们还是同一个对象
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
//…………
}
/**
* 4
* ThreadLocal类的方法
* 父线程的inheritableThreadLocals包装到子线程的inheritableThreadLocals中
*
* @param parentMap 父线程的inheritableThreadLocals
* @return ThreadLocalMap
*/
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
//5 新建ThreadLocalMap,包含指定的ThreadLocalMap的所有数据
return new ThreadLocalMap(parentMap);
}
/**
* ThreadLocalMap类的构造函数,包含指定的ThreadLocalMap的所有数据
*
* @param parentMap 指定的ThreadLocalMap对象
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
//获取table数组
ThreadLocalMap.Entry[] parentTable = parentMap.table;
//获取长度
int len = parentTable.length;
//设置扩容阀值
setThreshold(len);
//新建table
table = new ThreadLocalMap.Entry[len];
for (int j = 0; j < len; j++) {
ThreadLocalMap.Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
//这里终于调用到了重写的childValue方法
//用于把父线程的inheritableThreadLocals属性的数据复制到新的ThreadLocalMap对象中
Object value = key.childValue(e.value);
ThreadLocalMap.Entry c = new ThreadLocalMap.Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
3 总结和案例
InheritableThreadLocal 类重写了三个方法。createMap将创建的ThreadLocalMap赋值给具体线程的inheritableThreadLocals变量。getMap将返回具体线程的inheritableThreadLocals变量,从而让本地变量保存到了inheritableThreadLocals变量里面,那么线程在通过InheritableThreadLocal 类实例的set 或者get 方法设置变量时,就会创建当前线程的inheritableThreadLocals 变量。
当在父线程中创建子线程时,构造函数会把父线程中inheritableThreadLocals变量里面的本地变量复制一份(浅克隆)保存到子线程的inheritableThreadLocals 变量里面。
InheritableThreadLocal传递数据的案例:
public class InheritableThreadLocalTest {
static InheritableThreadLocal<InheritableThreadLocalTest> threadLocal = new InheritableThreadLocal<>();
public static void set() {
threadLocal.set(new InheritableThreadLocalTest());
}
public static InheritableThreadLocalTest get() {
return threadLocal.get();
}
public static void main(String[] args) throws InterruptedException {
System.out.println("主线程中尝试获取值====>" + get());
System.out.println("主线程中设置值====>");
set();
//主线程中尝试获取值
System.out.println("主线程中再次尝试获取值====>" + get());
System.out.println();
System.out.println("开启一条子线程====>");
Thread thread = new Thread(new Th1(), "child");
thread.start();
//主线程等待子线程执行完毕
thread.join();
System.out.println();
System.out.println("等待子线程执行完毕,主线程中再次尝试获取值====>" + get());
System.out.println("获取到的还是父线程原来的值,即父可以传递数据给子,子不能传递数据给父");
}
static class Th1 implements Runnable {
@Override
public void run() {
System.out.println("子线程中尝试获取值====>" + get());
System.out.println("能够直接获取到,说明父线程的数据传递给了子线程,并且获取到的对象和父类获取到的对象还是同一个,即浅拷贝");
System.out.println("子线程中设置值====>");
set();
System.out.println("子线程中再次尝试获取值====>" + get());
System.out.println("此时获取到的,是自己设置的值");
System.out.println();
}
}
@Override
public String toString() {
return this.hashCode() + "";
}
}
结果:
主线程中尝试获取值====>null
主线程中设置值====>
主线程中再次尝试获取值====>1878246837
开启一条子线程====>
子线程中尝试获取值====>1878246837
能够直接获取到,说明父线程的数据传递给了子线程,并且获取到的对象和父类获取到的对象还是同一个,即浅拷贝
子线程中设置值====>
子线程中再次尝试获取值====>185894025
此时获取到的,是自己设置的值
等待子线程执行完毕,主线程中再次尝试获取值====>1878246837
获取到的还是父线程原来的值,即父可以传递数据给子,子不能传递数据给父
可以看出,子线程可以正常获取到父线程本地变量的值。InheritableThreadLocal常常用在调用链路追踪上。
如果有什么不懂或者需要交流,可以留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!