java开发工作中对InheritableThreadLocal使用思考
作者:方圆想当图灵 发布时间:2023-11-24 21:46:44
引言
最近在工作中结合线程池使用 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线程 变量1
executorService.execute(() -> System.out.println(threadLocal.get()));
threadLocal.set("main线程 变量2");
// 线程复用再取还是 main线程 变量1
executorService.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方法
开启新线程时,会调用Thread的构造方法
public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}
沿着构造方法向下,找到
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);
...
}
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. 实验例子流程图
main线程set main线程 变量1时,会调用到
InheritableThreadLocal
的createMap
方法,创建inheritableThreadLocals
并保存线程变量开启子线程1时,会深拷贝父线程中的线程变量到子线程中,如图示
main线程set main线程 变量2,会覆盖主线程中之前set的mian线程变量1
最后发生线程复用,子线程1无法获取到main线程新set的值,仍然打印 main线程 变量1
来源:https://juejin.cn/post/7166046781216653326


猜你喜欢
- 我们在开发需求的时候,难免会接入一下第三方的H5页面,有些H5页面是具有上传照片的功能,Android 中的 WebView是不能直接打开文
- 一、前言最近在回顾数据结构与算法,有部分的算法题用到了栈的思想,说起栈又不得不说链表了。数组和链表都是线性存储结构的基础,栈和队列都是线性存
- 前言记录下SpringBoot修改yml配置文件后无需重启服务的方式(打包后生效),效果如下:具体实现实现代码pom.xml<depe
- 本文实例为大家分享了java实现酒店管理系统的具体代码,供大家参考,具体内容如下要求:【酒店管理系统】HotelSystem.java某酒店
- 本文实例为大家分享了java数字转汉字工具类的具体代码,供大家参考,具体内容如下/** * Created by 33303 on 2017
- 浅谈先来说一下“this指针”:C++中通过引入this指针解决该问题,暨:C++编译器给每个“非静态的成员函数”增加了一个隐藏的指针参数,
- 一般表单数据分为两类<form method="post" action="${pageContext.
- 前言springmvc中有两种很普遍的AOP实现:1.过滤器(Filter)2. * (Interceptor)本篇面对的是一些刚接触spr
- 在Java项目开发中,Maven是我们最常用的依赖管理和构建工具了!我们常常通过添加dependency节点,就能够很方便地加入依赖,而不需
- Spring Cache设置缓存条件原理从Spring3.1开始,Spring框架提供了对Cache的支持,提供了一个对缓存使用的抽象,通过
- 本文实例讲述了Aspectj框架。分享给大家供大家参考,具体如下:一 环境变量配置CLASSPATH配置为:.;d:\aspectj1.8\
- 概述状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。这个模式将状态封装成独立的类,并将动作委托到 代表当前状态的对
- 快速普及1、mybatis是什么 mybatis是一个支持普通SQL查询,存储过
- 一、new 对象的几种说法初学 Java 面向对象的时候,实例化对象的说法有很多种,我老是被这些说法给弄晕。public class Tes
- 生产者工程POM依赖可以在创建工程时直接选择添加依赖。application文件因为rabbitmq具有默认地址及用户信息,所以如果是本地r
- JavaBean根据指定条件设置属性值默认值使用场景当bean数据中已经装配好其他数据,在逻辑以及数据转换完成的最后一步进行数据默认值设置;
- 介绍微服务横行的互联网世界, 跨服务调用显得很平凡, 我们除了采用传统的http方式接口调用, 有没有更为优雅方便的方法呢?答案是肯定的,f
- 记录一下工作流的在Springboot中的使用,,顺便写个demo,概念,什么东西的我就不解释了,如有问题欢迎各位大佬指导一下。1.创建sp
- 先说一下对异步和同步的理解:同步调用:调用方在调用过程中,持续等待返回结果。异步调用:调用方在调用过程中,不直接等待返回结果,而是执行其他任
- 1. 开方:Math.sqrt(x);2. x的a方:Math.pow(x,a);3. 绝对值:Math.abs(x);4. BigInte