大家好,我是方圆。最近在工作中结合线程池使用 InheritableThreadLocal 出现了获取线程变量“错误”的问题,看了相关的文档和源码后在此记录。

1. 先说结论

InheritableThreadLocal 只有在父线程创建子线程时,在子线程中才能获取到父线程中的线程变量;当配合线程池使用时:“第一次在线程池中开启线程,能在子线程中获取到父线程的线程变量,而当该子线程开启之后,发生线程复用,该子线程仍然保留的是之前开启它的父线程的线程变量,而无法获取当前父线程中新的线程变量”,所以会发生获取线程变量错误的情况。

2. 实验例子

  • 创建一个线程数固定为1的线程池,先在main线程中存入变量1,并使用线程池开启新的线程打印输出线程变量,之后更改main线程的线程变量为变量2,再使用线程池中线程(发生线程复用)打印输出线程变量,对比两次输出的值是否不同
/*** 测试线程池下InheritableThreadLocal线程变量失效的场景*/
public class TestInheritableThreadLocal {private static final InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();// 固定大小的线程池,保证线程复用private static final ExecutorService executorService = Executors.newFixedThreadPool(1);public static void main(String[] args) {threadLocal.set("main线程 变量1");// 正常取到 main线程 变量1executorService.execute(() -> System.out.println(threadLocal.get()));threadLocal.set("main线程 变量2");// 线程复用再取还是 main线程 变量1executorService.execute(() -> System.out.println(threadLocal.get()));}
}

输出结果:

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 源码

public class InheritableThreadLocal<T> extends ThreadLocal<T> {/*** 当子线程被创建时,通过该方法来初始化子线程中线程变量的值,* 这个方法在父线程中被调用,并且在子线程开启之前。* * 通过重写这个方法可以改变从父线程中继承过来的值。** @param parentValue the parent thread's value* @return the child thread's initial value*/protected T childValue(T parentValue) {return parentValue;}ThreadLocalMap getMap(Thread t) {return t.inheritableThreadLocals;}void createMap(Thread t, T firstValue) {t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);}
}

其中childValue方法来获取父线程中的线程变量的值,也可通过重写这个方法来将获取到的线程变量的值进行修改。

getMap方法和createMap方法中,可以发现inheritableThreadLocals变量,它是 ThreadLocalMap,在Thread类

3.2.1 childValue方法

  1. 开启新线程时,会调用Thread的构造方法
    public Thread(ThreadGroup group, String name) {init(group, null, name, 0);}
  1. 沿着构造方法向下,找到init方法的最终实现,其中有如下逻辑:为当前线程创建线程变量以继承父线程中的线程变量
/*** @param inheritThreadLocals 为ture,代表是为 包含可继承的线程变量 的线程进行初始化*/
private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {...if (inheritThreadLocals && parent.inheritableThreadLocals != null)// 注意这里创建子线程的线程变量this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);...}
  1. ThreadLocal.createInheritedMap(parent.inheritableThreadLocals)创建子线程 InheritedMap 的具体实现

createInheritedMap 方法,最终会调用到 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) {// 注意!!! 这里调用了childValue方法Object value = key.childValue(e.value);Entry c = new Entry(key, value);int h = key.threadLocalHashCode & (len - 1);while (table[h] != null)h = nextIndex(h, len);table[h] = c;size++;}}}}

这个方法会对父线程中的线程变量做拷贝,其中调用了childValue方法来获取/初始化子线程中的值,并保存到子线程中

  • 由上可见,可继承的线程变量只是在线程被创建的时候进行了初始化工作,这也就能解释为什么在线程池中发生线程复用时不能获取到父线程线程变量的原因

4. 实验例子流程图

  1. main线程set main线程 变量1时,会调用到InheritableThreadLocalcreateMap方法,创建 inheritableThreadLocals 并保存线程变量
  2. 开启子线程1时,会拷贝父线程中的线程变量到子线程中,如图示
  3. main线程set main线程 变量2,会覆盖主线程中之前set的mian线程变量1
  4. 最后发生线程复用,子线程1无法获取到main线程新set的值,仍然打印 main线程 变量1

5. 解决方案: TransmittableThreadLocal

使用阿里巴巴 TransmittableThreadLocal 能解决线程变量线程封闭的问题,测试用例如下,在线程池提交任务时调用TtlRunnableget方法来完成线程变量传递

public class TestInheritableThreadLocal {private static final TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();// 固定大小的线程池,保证线程复用private static final ExecutorService executorService = Executors.newFixedThreadPool(1);public static void main(String[] args) {threadLocal.set("main线程 变量1");// 正常取到 main线程 变量1executorService.execute(() -> System.out.println(threadLocal.get()));threadLocal.set("main线程 变量2");// 使用TransmittableThreadLocal解决问题executorService.execute(TtlRunnable.get(() -> System.out.println(threadLocal.get())));executorService.shutdown();}
}

输出结果:
main线程 变量1
main线程 变量2

  • 注意:对象类型需要注意线程安全问题
  • 具体用法参考 https://github.com/alibaba/transmittable-thread-local

That’s all.

工作中对InheritableThreadLocal使用的思考相关推荐

  1. 1期精彩推荐:如何应对工作中的冲突?

    问题:在人们的日常工作和生活中,沟通是非常重要的.软件开发的工作也是如此,既然是沟通,就很有可能会发生冲突,那么技术人员应该如何应对工作中与别人发生的冲突呢?   特邀嘉宾: 胡百师:中国微软培训中心 ...

  2. (转)如何应对工作中的冲突?

    问题:在人们的日常工作和生活中,沟通是非常重要的.软件开发的工作也是如此,既然是沟通,就很有可能会发生冲突,那么技术人员应该如何应对工作中与别人发生的冲突呢?   特邀嘉宾: 胡百师:中国微软培训中心 ...

  3. 如何应对工作中的冲突?

    摘自<程序员>官网 问题:在人们的日常工作和生活中,沟通是非常重要的.软件开发的工作也是如此,既然是沟通,就很有可能会发生冲突,那么技术人员应该如何应对工作中与别人发生的冲突呢? 胡百师: ...

  4. renpy 如何执行2个action_如何解决工作中遇到问题丨2个思考方式、2个技巧和1个解决系统...

    2020年我遇到两次工作中出现了很麻烦的问题,第一次我是感觉到似乎天都要塌了,这要是解决不了我该怎么办呢?焦虑的情绪比问题还要更难解决,我不止一次流泪想着要是解决不了怎么办啊?我闺蜜说你一个老师,再大 ...

  5. 成长的思考:如何在工作中保持高速的自我成长

    之前在跟一位正在读博士的同学交流之后,我意识到了自己成长的危机.那么如果我选择了不去读博士,那么我又该如何保持快速的自我成长呢?如何保持与博士同学们的思维层次和眼界在同一个水平上呢?如何在相同的时间内 ...

  6. 如何做好工作中的思考沉淀?

    个人的成长离不开平时的点滴收获,平时的收获离不开工作中的思考总结.只有掌握好的思考沉淀方法,才能帮助我们快速的获得提高. 有很多种方法可以做好思考沉淀,其中工作感想就是一种很好的帮助自我认知迭代,思考 ...

  7. “学而不思则罔,思而不学则殆”在实施工作中的思考

    这句话好像是初中学的,当时老师教的解释是:学习而不思考就会迷茫,只思考而不学习就会危险走入歧途. 现在在实施工作中,应当提醒用户用这样的思维方式去学习.思考软件.也就是学习软件的操作和流程,同时要思考 ...

  8. 关于提BUG的一点思考以及工作中总结的规范

    在测试的工作中,提BUG是日常工作. 以前自己为了省事,省时,仅仅是截图,在图片上注明一下问题,就放到BUG库中了. 现在发现这样会造成开发的时间的浪费,增加了沟通成本. 对于BUG,当发现了异常时, ...

  9. 工作中对数据分析思路的一点思考

    工作中,经常会遇到产品.运营等各方人员对某个数据的疑问,或者各种各样的数据需求和数据问题.对于数据从业者,我越来越意识到我们不仅仅需要掌握必要的编程基础和专业知识,也需要掌握一些常见的数据分析思路,进 ...

最新文章

  1. python安装在什么系统下最好-windows系统下Python环境的搭建
  2. python刷leetcode_零基础python刷leetcode -- 3. Longest Substring Without Repeating Characters
  3. 金融业如何更好地利用大数据实现突破性变革?(实例解读)
  4. 消息队列中点对点与发布订阅区别
  5. 新0-Day漏洞或将给Linux桌面发行版带来浩劫
  6. 【LCT】弹飞绵羊(luogu 3203/金牌导航 LCT-2)
  7. linux裸机串口,裸机系列-UART串口
  8. java.net.InetAddress 获取系统 MAC 地址 与 IP 地址
  9. docker容器启动失败解决办法
  10. linux grep查找指定文件或目录下文件的字符
  11. 计算机内录,如何电脑内部录音,介绍一款可以录制电脑内部声音的工具
  12. 常见的技术文档英文单词
  13. 魔方阵原理及十种解法(C语言)
  14. python抢票_抢票工具成了GitHub热榜第一,最新支持候补抢票,Python跑起来 | 标星8400...
  15. 豆瓣电影Top250信息爬取并保存到excel文件中!
  16. 谈谈我了解的那些在线it学习网站
  17. C语言实现求最小公倍数。
  18. 整合spring cloud云架构 - SSO单点登录之OAuth2.0登录流程
  19. Django教程 —— Django入门
  20. 谷歌图像爬虫方法总结与教程

热门文章

  1. 前后端分离技术之加签,验签,防篡改
  2. 为什么 Activity.finish() 之后 10s 才 onDestroy ?
  3. 198. House Robber抢劫房子Python Java
  4. SLF4J:Failed to load class org.slf4j.impl.StaticLoggerBinder.
  5. 简单的图书管理系统用例图(UML)
  6. 阿里人脸识别安全技术获专利可防范3D人脸面具攻击
  7. 2013:Linux的黄金之年-十大杰出成就
  8. python实现指定数据库指定表的数据同步(监听binlog)
  9. excel为单元格区域套用表格样式以及取消表格样式
  10. 基于Node.js的微信跳一跳辅助工具