工作中对 InheritableThreadLocal 使用的思考
作者:京东保险 王奕龙
代码评审时,发现在线程池中使用 InheritableThreadLocal 上下文会使其中的线程变量失效,无法获取到预期的变量值,所以对问题进行了复盘和总结。
1. 先说结论
InheritableThreadLocal
只有在父线程创建子线程时,在子线程中才能获取到父线程中的线程变量;当配合线程池使用时: “第一次在线程池中开启线程,能在子线程中获取到父线程的线程变量,而当该子线程开启之后,发生线程复用,该子线程仍然保留的是之前开启它的父线程的线程变量,而无法获取当前父线程中新的线程变量” ,所以会发生获取线程变量错误的情况。
2. 实验例子
创建一个线程数固定为 1 的线程池,先在 main 线程中存入
变量1
,并使用线程池开启新的线程打印输出线程变量,之后更改 main 线程的线程变量为变量2
,再使用线程池中线程(发生线程复用)打印输出线程变量,对比两次输出的值是否不同
输出结果:
main 线程 变量 1
main 线程 变量 1
发现两次输出结果值相同,证明发生线程复用时,子线程获取父线程变量失效
3. 详解
3.1 JavaDoc
This class extends ThreadLocal to provide inheritance of values from parent thread to child thread: when a child thread is created, the child receives initial values for all inheritable thread-local variables for which the parent has values. Normally the child's values will be identical to the parent's; however, the child's value can be made an arbitrary function of the parent's by overriding the childValue method in this class.
Inheritable thread-local variables are used in preference to ordinary thread-local variables when the per-thread-attribute being maintained in the variable (e.g., User ID, Transaction ID) must be automatically transmitted to any child threads that are created.
InheritableThreadLocal
继承了ThreadLocal
, 以能够让子线程能够从父线程中继承线程变量: 当一个子线程被创建
时,它会接收到父线程中所有可继承的变量。通常情况下,子线程和父线程中的线程变量是完全相同的,但是可以通过重写childValue
方法来使父子线程中的值不同。当线程中维护的变量如 UserId, TransactionId 等必须自动传递到新创建的任何子线程时,使用
InheritableThreadLocal
要优于ThreadLocal
3.2 源码
其中childValue
方法来获取父线程中的线程变量的值,也可通过重写这个方法来将获取到的线程变量的值进行修改。
在getMap
方法和createMap
方法中,可以发现inheritableThreadLocals
变量,它是 ThreadLocalMap
,在Thread类
中
3.2.1 childValue 方法
开启新线程时,会调用 Thread 的构造方法
沿着构造方法向下,找到
init
方法的最终实现,其中有如下逻辑:为当前线程创建线程变量以继承父线程中的线程变量
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals)
创建子线程InheritedMap
的具体实现
createInheritedMap
方法,最终会调用到 ThreadLocalMap
的私有构造方法,传入的参数 parentMap 即为父线程中保存的线程变量
这个方法会对父线程中的线程变量做拷贝,其中调用了childValue
方法来获取/初始化子线程中的值,并保存到子线程中
由上可见,可继承的线程变量只是在线程被创建的时候进行了初始化工作,这也就能解释为什么在线程池中发生线程复用时不能获取到父线程线程变量的原因
4. 实验例子流程图
main 线程
set main线程 变量1
时,会调用到InheritableThreadLocal
的createMap
方法,创建inheritableThreadLocals
并保存线程变量开启子线程 1 时,会拷贝父线程中的线程变量到子线程中,如图示
main 线程
set main线程 变量2
,会覆盖主线程中之前 set 的 mian 线程变量 1最后发生线程复用,子线程 1 无法获取到 main 线程新 set 的值,仍然打印 main 线程 变量 1
5. 解决方案: TransmittableThreadLocal
使用阿里巴巴 TransmittableThreadLocal
能解决线程变量线程封闭的问题,测试用例如下,在线程池提交任务时调用TtlRunnable
的get
方法来完成线程变量传递
输出结果:
main 线程 变量 1
main 线程 变量 2
注意:对象类型需要注意线程安全问题
具体用法参考 https://github.com/alibaba/transmittable-thread-local
That's all.
版权声明: 本文为 InfoQ 作者【京东科技开发者】的原创文章。
原文链接:【http://xie.infoq.cn/article/1f06ba082d149b1fa0fde0d71】。文章转载请联系作者。
评论