浅谈InheritableThreadLocal---可继承的小书包

news/2025/10/14 14:37:07/文章来源:https://www.cnblogs.com/jilodream/p/19141047

在前文中我们讲过ThreadLocal,相当于是每个线程有一个小书包,线程之间的小书包是隔离的,只存放了属于当前线程自己的变量,因此不会发生数据安全的问题。

(前文博客浅谈ThreadLocal----每个线程一个小书包  https://www.cnblogs.com/jilodream/p/19118986)

但是有时任务太繁重时,父线程希望new出新的子线程来为自己的业务提供帮助,同时希望子线程在处理时,也能用到自己保存在ThreadLocal中的变量。

现在有三种办法:
(1)直接把父线程的ThreadLocalMap传递给子线程,让子线程直接拿去用
如:

sonThread.threadLocals=parent.threadLocals

(2)父线程在创建子线程时将子线程需要用到的ThreadLocal数据,专门指定。

如:

 Thread sonThread=new Thread(new Runnable() {@Overridepublic void run() {threadLocal1.set(xxx1); //手动指定threadLocal2.set(xxx2); //手动指定threadLocal3.set(xxx3); //手动指定
            }});

(3)将父线程中threadLocals 中的数据,打包整理后,统一传递给子线程。

第一种显然不行,如果两者指向了相同的Map 会导致缓存数据被共享,父子线程存在并发读写,又会导致安全问题。
方法二虽然灵活,但是指定起来过于繁琐,每个子线程都要单独设置一遍。
java选用的是方法三,但是实现起来稍有不同:
java 定义了一个InheritableThreadLocal类。通过这个类来实现线程可继承的ThreadLocal
定义中的:
Inheritable [ɪn'herɪtəbl]
adj. 可继承的,会遗传的
InheritableThreadLocal指可以继承/遗传的ThreadLocal。接下来看源码:

在Thread类中,定义了以下属性:

    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; 

类似于thread.threadLocals,这是给InheritableThreadLocal 来存储可以继承的属性的Map。

InheritableThreadLocal类的源码,它继承了ThreadLocal:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {/*** Creates an inheritable thread local variable.*/public InheritableThreadLocal() {}/*** Computes the child's initial value for this inheritable thread-local* variable as a function of the parent's value at the time the child* thread is created.  This method is called from within the parent* thread before the child is started.* <p>* This method merely returns its input argument, and should be overridden* if a different behavior is desired.** @param parentValue the parent thread's value* @return the child thread's initial value*/protected T childValue(T parentValue) {return parentValue;}/*** Get the map associated with a ThreadLocal.** @param t the current thread*/ThreadLocalMap getMap(Thread t) {return t.inheritableThreadLocals;}/*** Create the map associated with a ThreadLocal.** @param t the current thread* @param firstValue value for the initial entry of the table.*/void createMap(Thread t, T firstValue) {t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);}
}

通过源码我们可以发现,它的用法和实现与ThreadLocal基本相同,但是它重写了父类(ThreadLocal)的几个方法:

(1)
首先是getMap(),重写的目的是当需要操作map对象时,(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )请使用inheritableThreadLocals而不是再是父类中的t.threadLocals写法,这样所有使用的map就都切换到了
ThreadLocal.ThreadLocalMap inheritableThreadLocals这个属性上来了。

(2)
其次是createMap() 方法,该方法的主要目的是初始化Map对象
这里强调了初始化的是 t.inheritableThreadLocals 属性。为啥没直接用getMap() 方法来获取 t.inheritableThreadLocals呢?这是由于 t.inheritableThreadLocals 还没有初始化,返回是null,你调用getMap()没有意义。
(3)
最后是 childValue() 方法,它是指当发生继承动作时,父类中的存储的变量转化为子类对象的转化转换。这里直接抽象成方法了,方便大家重写自己的InheritableThreadLocal类时,可以直接重写该方法。
如我们想前边加一个标签,代表是子线程专用,亦或者进行翻译转换等等,总之方便你自己实现转换。

来看下继承动作具体是怎么操作的:
继承主要发生在主线程创建子线程程时,我们来看下Thread的构造方法源码,经过一堆跳转最后会跳转进这个方法:

    private Thread(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {//....if (inheritThreadLocals && parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);/* Stash the specified stack size in case the VM cares */this.stackSize = stackSize;/* Set thread ID */this.tid = nextThreadID();}

方法参数中的boolean inheritThreadLocals表示是否要继承/遗传。手动创建的线程,这里都会是true,代表要继承/遗传。

parent.inheritableThreadLocals != null 判断父线程的inheritableThreadLocals 属性是否为null (是否初始化了),如果不为null(父线程有需要遗传的本地变量),才发生遗传动作。
条件都满足则发生遗传,(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )入参为父线程的遗传本地变量,也就是属性inheritableThreadLocals。

之后进入ThreadLocalMap的构造方法,开始构造子线程的inheritableThreadLocals 属性:

        private ThreadLocalMap(ThreadLocalMap parentMap) {Entry[] parentTable = parentMap.table;int len = parentTable.length;setThreshold(len);table = new Entry[len];for (Entry e : parentTable) {if (e != null) {@SuppressWarnings("unchecked")ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();if (key != null) {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++;}}}}

方法中就是遍历父线程的Map生成子线程自己的Map.Entry 了。
这里注意,在获取Entry的key 时,通过entry.get() 拿到父线程的Map 的key的弱引用,强制转化为ThreadLocal类型即可。
在获取value 值时,调用的是key的childValue()方法,也就是InheritableThreadLocal.childValue()中重写的方法,将父线程的value值转为子线程的value时。
这样子线程中:
1、map初始化好了;
2、entry元素也通过父线程都转移到子线程中了;
3、获取的遗传map,使用的都是遗传map了。(通过getMap()重写)
map的get set remove 等核心逻辑都直接使用父类的逻辑。(因为InheritableThreadLocal继承自ThreadLocal,并且没有重写这段逻辑)
总体上了来说,ThreadLocal,InheritableThreadLocal的实现都非常的优雅,不但很好的利用了对象的继承,保证用户在使用时无感知的发生了继承。其次用户在自定义自己的InheritableThreadLocal class时,(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )也只需要完成如何转化即可。非常值得我们自己在设计类结构和关系时参考这里的细节设计。

类关系整体的结构如下:

Itlleitu

除此之外,我们也可以通过源码发现InheritableThreadLocal的一个特性,就是属性的遗传来自于父InheritableThreadLocal的全量属性,是不能根据某个线程自定义的。

并且在遗传时,子线程是在被new出实例时,就已经获得了此刻全部的属性,如果父线程后续调整了InheritableThreadLocal的范围,子线程是感知不到的。
来看这样一个例子即可

    public static void main(String[] args) {ThreadLocal<String> tl1 = new InheritableThreadLocal<>();ThreadLocal<String> tl2 = new InheritableThreadLocal<>();ThreadLocal<String> tl3 = new InheritableThreadLocal<>();tl1.set("t1");tl2.set("t2");Thread sonThread = new Thread(new Runnable() {@Overridepublic void run() {String s1 = tl1.get();String s2 = tl2.get();String s3 = tl3.get();System.out.println("out:" + s1);System.out.println("out:" + s2);System.out.println("out:" + s3);}});tl2.set("newTl2Value");sonThread.start();}

输出如下,所有的值都是在new 子线程时就已经发生了,而并不是在子线程start之前:

out:t1
out:t2
out:null

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/936833.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何理解面向对象?

“深刻理解 C++ 的面向对象思想”其实是从 程序设计思想 层面上,真正掌握 C++ 精髓的标志。很多人学 C++ 只停留在“语法层面”(class、public、private、继承、多态),但真正的理解在于为什么这样设计、要解决什么…

2025 年湖南单招培训学校最新推荐榜单:口碑实力机构排行榜,聚焦高升学率与优质服务的精准选校指南单招无忧题库/单招培训学校推荐

2025 年湖南省单招报考热度持续攀升,部分高校专业报录比已达 470%,但培训行业却陷入 “选择困境”:新机构扎堆涌现却缺乏成熟教学体系,部分机构课程与考纲脱节,依赖兼职教师导致服务断层,更有甚者虚构升学率误导…

2025-10-14 el.style.backGroundColor = #ccc !important样式不生效??==》改为添加类

业务中常见有js操作样式,当我想给元素加个背景颜色时是能生效的,但如果加上了权重!important反而不生效了, 原因是这不是有效的颜色值,你可以把el.style.backGroundColor = #ccc !important改为el.classList.add(h…

mns 1014

今天 Dr. William Wallace Wettle 高端局。 A 开局不会做。搞掉 B 之后发现可以维护可能的温度的区间,秒了。点击查看代码 #include <bits/stdc++.h> using namespace std;struct node {int t, l, r; }arr[1000…

牛客周赛113

(0条未读通知) 牛客竞赛_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ E题 首先我们预处理每个数组从\(n\)个数中选择\(i\)个数,其和模495为\(j\)的方案数,可以使用三维\(dp[i][j][k]\)数组表示前\(i\)个数…

如何在统信系统中将 Avalonia 软件程序打包 Deb 安装包

如何在统信系统中将 Avalonia 软件程序打包 Deb 安装包一、简介    太久没有写博客了,不是不想写,而是太忙了。最近我在使用 Avalonia UI 框架开发一个跨平台的应用程序,Avalonia 本身来说,还好了,社区很活跃…

分组密码算法工作模式

安全需求 1.机密性需求 保密工作模式:ECB模式、CBC模式、CTR模式 2.完整性、不可否认性 认证工作模式:CMAC 3.机密性、完整性、不可否认性 加密认证工作模式:EtM算法、MtE算法、GCM模式 保密工作模式 ECB模式 电子码…

2025 年山西/在职研究生培训机构推荐榜:同等学力申硕培训机构,聚焦数智化与个性化学习新范式

随着终身学习理念普及和职场竞争加剧,2025 年在职研究生教育市场呈现出 “技术驱动、精准服务” 的新趋势。政策层面,非全日制研究生与全日制同等效力的落实,叠加 AI 技术在教育领域的深度渗透,推动行业从 “规模扩…

2025 年涡街流量计厂家推荐,湖北南控仪表科技有限公司技术创新与行业应用解决方案解析

行业背景在工业自动化进程不断加快的当下,流量测量作为工业生产中的关键环节,其准确性与稳定性直接影响企业的生产效率、成本控制及安全运营。涡街流量计凭借结构简单牢固、测量精度高、应用范围广等优势,已成为众多…

2025 年超声波流量计厂家推荐,湖北南控仪表科技有限公司产品技术与行业应用解决方案解析

在工业自动化与能源管理精细化发展的双重驱动下,超声波流量计凭借非接触式测量、高精度、低维护需求等优势,成为流量测量领域的核心设备。2025 年全球超声波流量计市场规模将突破 169.31 亿元,石油化工、市政供水、…

ArcGIS 10.2.2 字符串长度为20却仅能输入3个汉字的解决方法

ArcGIS 10.2.2 字符串长度为20却仅能输入3个汉字的解决方法问题: 字符串长度为20,却仅能输入3个汉字。 原因: 长度20为字节,非字符。 解决方法: 安装补丁 链接:https://pan.baidu.com/s/1MNobUc5FLqkGloNTuu_64w…

2025 年涡轮流量计厂家推荐:湖北南控仪表科技有限公司设备供应与多行业适配解决方案

在工业生产过程中,流量测量是把控生产流程、保障产品质量的关键环节,而涡轮流量计凭借测量精度高、响应速度快、量程范围广的特点,在液体、气体等介质的流量测量场景中应用广泛,涵盖节能、环保、市政工程、化工、核…

OAuth/OpenID Connect安全测试全指南

本文详细介绍了OAuth和OpenID Connect在现代Web应用中的安全测试案例,涵盖端点侦察、开放重定向、代码重放攻击、CSRF防护、令牌生命周期管理等关键测试场景和防护建议。Web应用渗透测试:OAuth/OpenID Connect测试案…

采用虚幻引擎(UE5)打造黑夜场景氛围

采用虚幻引擎(UE5)打造黑夜场景氛围pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Mona…

2025 年电磁流量计厂家推荐:湖北南控仪表科技有限公司专业设备供应与行业适配解决方案

行业背景在工业自动化快速发展的当下,流量测量作为工业生产中的关键环节,直接关系到生产效率、成本控制与产品质量。电磁流量计凭借其能精准测量导电液体流量、不受流体密度、粘度等参数影响的特性,在节能、环保、市…

90%企业忽略的隐藏成本:Data Agent如何降低数据分析总拥有成本(TCO)

引言:数据分析的“成本冰山”与决策者的认知盲区 一个令人深思的现实是:许多企业每年在商业智能(BI)平台上投入超过10万美元,却仍在为低下的工具采用率和决策效率而苦恼。根据 Querio.ai的分析,传统BI工具的投资…

adb logcat 根据Tag 过滤日志

adb logcat根据tag获取需要指定标签(tag)和日志级别adb logcat [TAG:LEVEL ] [TAG:LEVEL ] ... LEVEL: 可以选择:[V D I W E S]中其中一个 TAG:X 的作用为: 输出标签为TAG的log级别大于X的信息. 例如: adb logcat Te…

详细介绍:Spring Boot 详细介绍

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025 年艺术涂料厂家最新推荐排行榜,全方位呈现优质品牌特色与竞争力

随着家居装饰与商业空间装修需求的不断升级,艺术涂料凭借丰富的纹理、多样的色彩及良好的性能,成为众多消费者与合作商的优选装饰材料。然而,当前艺术涂料市场品牌繁杂,部分品牌存在产品质量参差不齐、工艺技术落后…

爬虫遇到的问题与解

学习爬虫过程中遇到的一些问题 requests的content和text方法的区别 Requests对象的get和post方法都会返回一个Response对象,这个对象里面存的是服务器返回的所有信息,包括响应头,响应状态码等。其中返回的网页部…