ThreadLocal系列(四):父子线程信息传递与TTL
父子线程信息传递与TTL前面介绍了ThreadLocal使用与内存泄漏防范还从引用队列角度思考如何防范内存泄漏。这篇文章是自己在实际中用到了RAG检索与回答用自定义线程池而不是tomcat线程池防止tomcat线程池线程被占用导致无法处理其他请求。其中用到了跨线程的上下文传递就写个文章说一下。父子线程传递ThreadLocal问题ThreadLocal做上下文传递每个线程拿自己的用户信息互不干扰但如果子线程需要拿父线程的信息呢先做个测试public class Problem { private static final ThreadLocalString USER new ThreadLocal(); public static void main(String[] args) { USER.set(张三); System.out.println(主线程用户 USER.get()); new Thread(()-{ System.out.println(子线程用户 USER.get()); }).start(); } }结果根据前面的内容每个线程都有自己的ThreadLocal所以可以理解下面的结果主线程用户张三 子线程用户null怎么解决这个问题InheritableThreadLocalJDK提供了InheritableThreadLocal子线程能从父线程复制一份public class TestInheritable { private static final InheritableThreadLocalString USER new InheritableThreadLocal(); public static void main(String[] args) { USER.set(张三); System.out.println(主线程用户 USER.get()); new Thread(()-{ System.out.println(子线程用户 USER.get()); }).start(); } }运行结果主线程用户张三 子线程用户张三拿到了为什么可以拿到分析父子线程传递其实从第一篇文章就有体现在翻找第一篇文章的下面内容的时候就找到了这个InheritableThreadLocal第一篇的内容public class Thread implements Runnable { //省略。。。 /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals null; //省略。。。 }就在它的下方就有这个InheritableThreadLocal它也是Thread内部的进入Thread有俩个init方法看的是第二个private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null, true); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { // 省略。。。 if (inheritThreadLocals parent.inheritableThreadLocals ! null) // 关键parent的inheritableThreadLocals中复制过来 this.inheritableThreadLocals ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); this.stackSize stackSize; // 省略。。。 }进入createInheritedMapstatic ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); }再进去ThreadLocalMapprivate ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable parentMap.table; // 拿到它爹的Entry数组 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) ThreadLocalObject key (ThreadLocalObject) e.get(); //这个key没被回收 if (key ! null) { // 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; } } } }从上面看到return new ThreadLocalMap(parentMap)是把父线程的map放了进去进行遍历key没被回收的话就调用key.childValue方法得到新的value再给EntryEntry是ThreadLocalMap的内部类用于存储ThreadLocal的key和value。再进入childValue方法不过不是进入上面这个childValue方法而是进入InheritableThreadLocal的childValue方法。public class InheritableThreadLocalT extends ThreadLocalT { protected T childValue(T parentValue) { return parentValue; } //省略。。。 }这个方法是把传进来的e.value返回了回去一些细节ThreadLocalObject key (ThreadLocalObject) e.get(),e.get()的e是Entry类型的。前面第三个文章提到ntry的get是构造方法的时候通过super构造的而Entry又继承了WeakReference而WeakReference构造方法又调用了super,WeakReference又继承了ReferenceReference里有个private T referent;这个key层层super上去其实就是这个referent。整个过程没有new也就是说ThreadLocalObject key (ThreadLocalObject) e.get()中key和e.get()指的是堆里同一个对象。前面也提到key指的是ThreadLocal对象再往下看Object value key.childValue(e.value);而childValue在InheritableThreadLocal实现的是直接return了和上面key一样也是复制引用不复制对象再往下看Entry c new Entry(key, value)它new了也就是父的Entry和子的Entry不是指向同一个堆里的对象但是Entry里的key和value都是复制引用而没有复制对象所以Entry实际上也是浅拷贝。整个流程父子线程信息传递的时候父线程有个inheritableThreadLocals子线程Thread在init的时候拷贝父线程的ThreadLocalMap信息浅拷贝这样就获取了父线程的ThreadLocal的Entry每个Entry都有key和valueInheritableThreadLocal的问题从上面看出它获取父线程的值的时候是在自己这个线程创建的时候调用init方法也就是创建时复制提交时不复制意思就是线程池里复用的线程想拿父线程的信息只能在初始化的时候拿一次之后父线程ThreadLocalMap里新增了其他Entry子线程它拿不到了public class InheritableThreadLocalProblem { // 说明InheritableThreadLocal的问题 private static final InheritableThreadLocalString USER new InheritableThreadLocal(); public static void main(String[] args) throws InterruptedException { // 用线程池做线程复用 ExecutorService pool Executors.newFixedThreadPool(2); // 主线程set值 USER.set(张三); //提交到线程池线程池创建核心线程创建线程会获取父线程信息 pool.submit(()-{ System.out.println(线程池用户为 USER.get()); }); //主线程更新 USER.set(李四); pool.submit(()-{ System.out.println(线程池用户为 USER.get()); }); //线程池执行任务是异步的防止main直接没了导致线程池任务没跑就关掉 Thread.sleep(1000); pool.shutdown(); } }执行结果线程池用户为张三 线程池用户为李四输出是正确的因为核心线程弄了俩set之后pool里调用的时候创建了新线程没有复用。多执行几次这次三次set同时把线程池哪个线程执行的也打印出来。public class InheritableThreadLocalProblem { // 说明InheritableThreadLocal的问题 private static final InheritableThreadLocalString USER new InheritableThreadLocal(); public static void main(String[] args) throws InterruptedException { // 用线程池做线程复用 ExecutorService pool Executors.newFixedThreadPool(2); // 主线程set值 USER.set(张三); //提交到线程池线程池创建核心线程创建线程会获取父线程信息 pool.submit(()-{ System.out.println(Thread.currentThread().getName() 用户 USER.get()); }); //主线程更新 USER.set(李四); pool.submit(()-{ System.out.println(Thread.currentThread().getName() 用户 USER.get()); }); USER.set(王五); pool.submit(()-{ System.out.println(Thread.currentThread().getName() 用户 USER.get()); }); //线程池执行任务是异步的防止main直接没了导致线程池任务没跑就关掉 Thread.sleep(1000); pool.shutdown(); } }执行结果符合预期主线程更新了用户信息线程池却并没有进行更新。pool-1-thread-1 用户张三 pool-1-thread-2 用户李四 pool-1-thread-1 用户张三解决方案为什么解决场景Tomcat线程池复用核心线程执行任务的时候调用线程池执行耗时长的任务而线程池又需要Tomcat当前线程的上下文信息这就需要上下文信息支持跨线程传递TransmittableThreadLocalTTLTransmittableThreadLocal专门解决线程池场景的传递问题先导入依赖dependency groupIdcom.alibaba/groupId artifactIdtransmittable-thread-local/artifactId version2.14.5/version /dependency测试public class TTLTest { private static final ThreadLocalString USER new TransmittableThreadLocal(); public static void main(String[] args) throws InterruptedException { // 用线程池做线程复用 ExecutorService pool Executors.newFixedThreadPool(2); //装饰线程池用ttl执行任务 ExecutorService ttlPool TtlExecutors.getTtlExecutorService(pool); // 主线程set值 USER.set(张三); //提交到线程池 ttlPool.submit(() - { System.out.println(Thread.currentThread().getName() 用户 USER.get()); }); //主线程更新 USER.set(李四); ttlPool.submit(() - { System.out.println(Thread.currentThread().getName() 用户 USER.get()); }); USER.set(王五); ttlPool.submit(() - { System.out.println(Thread.currentThread().getName() 用户 USER.get()); }); //线程池执行任务是异步的防止main直接没了导致线程池任务没跑就关掉 Thread.sleep(1000); pool.shutdown(); } }结果pool-1-thread-1 用户张三 pool-1-thread-2 用户李四 pool-1-thread-1 用户王五

相关新闻

园区网关表免集中器怎么接?4G 母表带子表拓扑

园区网关表免集中器怎么接?4G 母表带子表拓扑

园区网关表,通常指的是一台带 4G 上行的母表,经 RS485 带多台子表、替现场省掉独立集中器的方案。该方案适用于楼栋集中、不想动原有布线的老旧园区。摘要:本文介绍园区网关表方案——以一台带 4G 上行的母表经 RS485 带多台子表,…

2026/10/9 5:38:42 阅读更多 →
软件测试与开发:思维模式、技术栈及职业发展的深度对比

软件测试与开发:思维模式、技术栈及职业发展的深度对比

1. “测试就是点点点”这句话,坑了多少人我在这个行业里待了十几年,见过不少刚入行的新人,也带过不少团队。一个特别有意思的现象是:很多开发同学对测试工作的认知,基本停留在“他们就是点点点,找找bug&…

2026/10/9 5:38:42 阅读更多 →
民心与文脉:穿透华夏千年兴衰的终极密码

民心与文脉:穿透华夏千年兴衰的终极密码

摘要 纵观华夏千年王朝更迭与近代势力兴衰,战争胜负、兵力强弱、疆域大小并非政权成败的核心决定因素。 真正决定历史走向的恒定规律,在于政权是否顺应底层民心、是否扎根华夏本土文脉。 本文以近代多方势力兴衰为样本,逐条拆解历史轮回底层…

2026/10/9 5:37:41 阅读更多 →

最新新闻

SpringBoot集成Hyperledger Fabric实现DID去中心化身份认证

SpringBoot集成Hyperledger Fabric实现DID去中心化身份认证

简介:本资源是一套面向本科毕业设计的分布式身份认证系统用户端实现,基于Hyperledger Fabric区块链构建可信身份管理体系,适用于信息安全、区块链开发与Java后端方向的学习者与毕设开发者。项目采用SpringBoot框架搭建,完整覆盖用…

2026/10/9 6:02:59 阅读更多 →
Linux进程间通信从原理到实战:共享内存与信号量完整指南

Linux进程间通信从原理到实战:共享内存与信号量完整指南

凡是常年跟Linux多进程程序打交道的人,早晚都会碰到一个绕不开的话题:进程间通信(IPC)。你可能已经见过进程间通信这个词无数次了,但真正在代码里用起来,尤其是要在性能、可靠性、复杂度三者之间做取舍时&a…

2026/10/9 6:02:59 阅读更多 →
旅游景点方面级情感分析实战:从语料构建到BERT模型调优

旅游景点方面级情感分析实战:从语料构建到BERT模型调优

简介:面向计算机相关专业学生完成毕业设计或课程设计,这份资源围绕旅游景点评论的方面级别情感分析任务,给出从语料库、模型训练到Django Web展示的完整源码方案。项目后端使用Django框架,涵盖数据库与ORM设计、评论文本预处理、情…

2026/10/9 6:02:59 阅读更多 →
时间序列预测实战:基于PyTorch统一框架对比LSTM、Transformer与自定义模型

时间序列预测实战:基于PyTorch统一框架对比LSTM、Transformer与自定义模型

简介:面向计算机相关专业学生和毕业设计开发者,资源以ETTh1电力负荷数据集为对象,提供了LSTM、Transformers以及自定义线性模型三种时间序列预测实现,用户可通过调整模型名称、序列长度等超参数对比不同架构的预测效果&#xff0c…

2026/10/9 6:02:59 阅读更多 →
AI写作全流程拆解:诘问、协议、生成三环节打造内容创作SOP

AI写作全流程拆解:诘问、协议、生成三环节打造内容创作SOP

当我的工作台同时贴上三张便签——“为什么必须写这个”“按什么规则写”“生成完谁来审”——我突然意识到,过去半年反复打磨的AI辅助创作流程,本质上是一套由“诘问、协议、生成”拼起来的流水线。我把它整理成《元创力》纪实录的第六卷,主…

2026/10/9 6:02:59 阅读更多 →
OKL4微内核源码深度拆解:从IPC到用户态驱动设计

OKL4微内核源码深度拆解:从IPC到用户态驱动设计

简介:OKL4 1.4.1.1 是微内核领域早期颇具代表性的发行版,适合操作系统课程学习者、嵌入式系统开发者以及想深入理解内核机理的工程师。资源以 tar.gz 压缩格式打包,整体约 58.71MB,解开后即可按目录查看完整源码结构。目前已有 94…

2026/10/9 6:01:59 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 10:10:36 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 13:34:55 阅读更多 →