目录
1、前言
2、回顾ThreadLocal
3、InheritableThreadLocal
4、实现原理
5、线程池中的问题
6、小结
1、前言
在《【JUC基础】14. ThreadLocal》一文中,介绍了ThreadLocal主要是用于每个线程持有的独立变量。通俗的说就是ThreadLocal是每个线程独有的一份内存,且各个线程间是独立、隔离的。但是随之而来的便会带来如下问题:
- 如果项目实际场景中,确实需要子线程与父线程共享或复用变量时候,就无法满足。
上面问题的一个解法就是我们今天要介绍的InheritableThreadLocal。
2、回顾ThreadLocal
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set("我是主线程的threadlocal变量");
System.out.println("-----> 主线程" + Thread.currentThread() + " <----- 获取threadlocal变量:" + threadLocal.get());
new Thread(() -> {
System.out.println("-----> 子线程" + Thread.currentThread() + " <----- 获取threadlocal变量:" + threadLocal.get());
}, "son-thread").start();
}
执行结果:
可以看出子线程想要获取父线程的threadlocal变量,是获取不到的。
3、InheritableThreadLocal
前面介绍了背景,那么InheritableThreadLocal是啥呢?他可以做一些啥?从类注释上可以看出InheritableThreadLocal实现了ThreadLocal的扩展,以提供从父线程到子线程的值继承。当创建子线程时,子线程接收父线程有值的所有可继承的线程局部变量的初始值。当在变量中维护每线程属性(例如,User ID)时,优先使用可继承的线程局部变量,而不是普通的线程局部变量。
我们将上面ThreadLocal的demo中,ThreadLocal改为InheritableThreadLocal试下:
static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
threadLocal.set("我是主线程的threadlocal变量");
System.out.println("-----> 主线程" + Thread.currentThread() + " <----- 获取threadlocal变量:" + threadLocal.get());
new Thread(() -> {
System.out.println("-----> 子线程" + Thread.currentThread() + " <----- 获取threadlocal变量:" + threadLocal.get());
}, "son-thread").start();
}
执行结果:
可以发现,主线程的变量成功穿透到子线程中。
4、实现原理
结果都看到了,但是我们肯定不能只满足于结果,我们来探究一下他是如何实现的。我们点进去InheritableThreadLocal可以看到,他是ThreadLocal的扩展,且重新实现了childValue(),getMap(),createMap()三个方法。
我们查看createMap()方法,可以看到inheritableThreadLocals变量其实是Thread内部定义的用于线程间共享(inheritable英译:遗传)的变量。
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
Thread.java:
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
接着查看inheritableThreadLocals是从哪里赋值的:
重点关注画圈的部分,点进去java.lang.Thread#init(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long, java.security.AccessControlContext, boolean),查看代码420行:
...
// 判断inheritThreadLocals为true,我们创建线程new Thread会进入初始化init方法,默认是true
// 且判断parent.inheritableThreadLocals不为空
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
// 进入该判断,将父线程的inheritableThreadLocals变量赋值给当前线程的inheritableThreadLocals
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
以上的源码就是InheritableThreadLocal如何实现父子线程变量共享的实现原理了。
5、线程池中的问题
其实不难看出,InheritableThreadLocal只是解决了父子线程共享,或者变量传递的问题。接下来我们改造一下代码,我们通过线程管理多个线程试试看,然后把threadlocal的赋值操作放在创建线程之后:
static ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
// 定义线程池,核心线程数为1,方便线程复用
static ExecutorService executorService = Executors.newSingleThreadExecutor();
public static void main(String[] args) throws InterruptedException {
// 线程池执行子线程
executorService.submit(() -> {
System.out.println("-----> 子线程" + Thread.currentThread() + " <----- 获取threadlocal变量:" + threadLocal.get());
});
// 主线程睡眠3s,模拟运行
Thread.sleep(3000);
// 将变量修改为11111
threadLocal.set("我是主线程的threadlocal变量,变量值为:11111");
// 这里线程池重新执行线程任务
executorService.submit(() -> {
System.out.println("-----> 子线程" + Thread.currentThread() + " <----- 获取threadlocal变量:" + threadLocal.get());
});
// 线程池关闭
executorService.shutdown();
}
执行结果:
怎么又拿不到了?没错,上面提到InheritableThreadLocal实现值传递主要是根据父线程的map是否有值,再决定要不要赋值给子线程。而父线程的map是通过init一个Thread的时候赋值的。如果我们新创建一个线程,那么肯定会出发创建的初始化方法,必然会进行赋值操作。但是线程池由于线程复用,重复使用的线程在执行异步任务时可能无需再执行创建方法了,因此也就不会再传递父线程的TLMap给子线程了。自然后面获取到的就是null了。
总而言之,就是InheritableThreadLocal进行传递的必须是线程创建的时候赋值的才可以,如果是异步任务中进行赋值的一样是获取不到。如果是线上环境,那么此类问题一般都是偶发的,很容易把你搞脱发。
看到这,我知道你很急,但是你别急。太阳底下无新鲜事,我们不是第一个遇到此类问题的人,别人肯定也遇到过,看看业界是如何实现的。这就是我们接下来要介绍的TransmittableThreadLocal。欲知后事如何,请听下回分解~
6、小结
JUC编程中,往往遇到的问题都不是必现的,具备一定的JUC相关技术基础,可以给你在排障的路上减少一些阻碍。一起学习进步吧。