[Java 并发编程] ThreadLocal 原理

ThreadLocal 原理

1. ThreadLocal 基础使用

​ ThreadLocal 被称为线程本地变量类,当多线程并发操作线程本地变量时,实际上每个线程操作的是其独立拥有的本地值,可以理解为每个线程分别独立维护自己的副本。这样就规避了线程安全问题,从而达到无锁并发。

​ 先来一个简单的使用示例:

@DatapublicclassUserContextExample{@DatastaticclassUserContext{// 定义一个用户上下文类privatefinalintuserId;privatefinalintsessionId;}// 设置为线程本地变量privatestaticfinalThreadLocal<UserContext>USER_CONTEXT_THREAD_LOCAL=newThreadLocal<>();// 设置当前线程的用户上下文publicstaticvoidsetUserContext(intuserId,intsessionId){USER_CONTEXT_THREAD_LOCAL.set(newUserContext(userId,sessionId));}// 获取当前线程的用户上下文publicstaticUserContextgetUserContext(){returnUSER_CONTEXT_THREAD_LOCAL.get();}// 清除当前线程的用户上下文publicstaticvoidclearUserContext(){USER_CONTEXT_THREAD_LOCAL.remove();}// 模拟 Web 请求publicstaticvoidmain(String[]args)throwsInterruptedException{ExecutorServicepool=Executors.newFixedThreadPool(5);for(inti=0;i<10;i++){finalintrequestId=i;pool.execute(()->{try{// 模拟从请求中获取用户信息setUserContext(requestId%3+1,requestId);// 拿到线程本地变量UserContextcontext=getUserContext();// 业务逻辑System.out.println(Thread.currentThread().getName()+" - Processing request for user: "+context.getUserId()+", session: "+context.getSessionId()+", requestId: "+requestId);Thread.sleep(100);}catch(InterruptedExceptione){Thread.currentThread().interrupt();}finally{// 必须清理,这一步尤为关键clearUserContext();}});}// 关闭线程池pool.shutdown();if(!pool.awaitTermination(10,TimeUnit.SECONDS)){pool.shutdownNow();}}}

​ 可以看出,每个线程都独立维护了USER_CONTEXT_THREAD_LOCAL的值 ,相当于这样的结构:

2. 应用场景

  1. 线程隔离

    这是 ThreadLocal 的最主要应用场景,常见的有:数据库连接管理,Session 数据管理。

    对于数据库连接来说,一般完成数据库操作后就要将连接关闭,如果连接不是线程独享的,那么当一个线程完成数据库操作后就不能直接关闭连接,因为尚可能有其他线程连接着该数据库。

  2. 跨函数传递数据

    一个线程设置 ThreadLocal 之后,对于这个线程的任何方法来说,都可以直接获取到其值,而无需通过方法参数传递。这通常适用于一些需要在函数之间频繁传输的数据。

3. ThreadLocal 原理

​ ThreadLocal 中使用了一个重要的数据结构用以维护众多线程的本地变量,称为 ThreadLocalMap。这个数据结构和 HashMap 的区别是,它使用了开放寻址法,而非 HashMap 的链地址法,并且它节点中的 key 均为弱引用包装过的,这个很重要,后面会说到。

​ ThreadLocal 提供的主要 API 其实都是在操作 ThreadLocalMap。其结构如下所示:

​ 每个 Thread 实例拥有一个 Map 实例,每个 Map 实例中有许多 ThreadLocal 实例作为 key,对应的 val 为该 Map 所属 Thread 独立维护的版本。

​ 从逻辑上讲,ThreadLocalMap 应当属于 Thread,但在代码层面 ThreadLocalMap 是作为静态内部类存在于 ThreadLocal 中,这容易让人误以为 ThreadLocalMap 属于 ThreadLocal。这其实是历史遗留问题,在早期的 JDK 版本中,ThreadLocalMap 的确是属于 ThreadLocal 的,也就是每个 ThreadLocal 实例都持有一个 ThreadLocalMap 实例,Map 里面以线程为 key,对应的 val 自然就是该线程维护的版本。这种方案的问题在于,在大部分的应用中,往往线程数是 ThreadLocal 实例数的十倍甚至百倍,如果以线程作为 key,Map 可能需要经常扩容,这样效率就比较低了。因此 JDK8 开始,已经将 ThreadLocalMap 在逻辑上归给 Thread,作为 Thread 的属性存在:ThreadLocal.ThreadLocalMap threadLocals;,不过 ThreadLocalMap 的源码依然存在于 ThreadLocal 类。

​ ThreadLocalMap 的节点使用弱引用进行了包装:

staticclassEntryextendsWeakReference<ThreadLocal<?>>{/** The value associated with this ThreadLocal. */Objectvalue;Entry(ThreadLocal<?>k,Objectv){super(k);value=v;}}

​ 这个弱引用是什么意思呢?就拿刚刚的USER_CONTEXT_THREAD_LOCAL为例,我们知道这是一个引用,而且是强引用,引用的实例就是new ThreadLocal<>(),只要这个引用还存在,实例就不会被 GC 回收。ThreadLocalMap 的 key 也是一个引用,但它是被WeakReference类包装的。规则是,如果一个实例仅存在弱引用,下一次 GC 就会回收它。引用我们可以理解为一种对实例的追踪方式,弱引用就是一类不会影响 GC 的追踪方式。

privatestaticvoidrefTest(){ObjectstrongRef=newObject();// 强引用WeakReference<Object>weakRef=newWeakReference<>(strongRef);// 弱引用System.gc();System.out.println(strongRef);// java.lang.Object@46f7f36aSystem.out.println(weakRef.get());// java.lang.Object@46f7f36astrongRef=null;System.gc();System.out.println(weakRef.get());// null}

​ 因此,如果这样写USER_CONTEXT_THREAD_LOCAL = null,那么实例就会被回收了。但事实上我们是没办法这样写的,因为已经将其设为 final 了,不能更改了。

​ 需要注意的是,若实例被回收,entry 的 key 变为 null 之后,value 仍然强引用在 entry 中,当后续调用setgetremove这些方法时,在方法内部才会触发这些 key 为 null 的 entry 的清理,也就是惰性清理的模式。因此,如果线程一直不终止(例如线程池中的线程),并且没有调用 ThreadLocal 的setgetremove来触发清理,value 会一直存在,造成 value 的内存泄漏。

​ ThreadLocal 在规范上要设为 static final,因为从语义上来说,ThreadLocal 本身并不存储数据,而是作为键来访问每个线程的 ThreadLocalMap 中的值。一个 ThreadLocal 实例应该对应于一个特定类型的线程局部变量,这个对应关系是全局唯一且不变的,因此用 static 保证一个特定类型的 ThreadLocal 的全局唯一性。final 是为了不使外部修改其引用,一旦引用被修改,如USER_CONTEXT_THREAD_LOCAL = new ThreadLocal<>(),那么原来的实例由于没有强引用了,就会被回收,进而 ThreadLocalMap 中原来指向旧实例的 key 指向 null,进而无法访问原先的 val,造成数据丢失。

​ 这里就有点矛盾,将 ThreadLocal 设为 final 会导致其永远存在强引用,ThreadLocal 实例就永远不会自动释放,key 就永远不指向 null,val 就永远不被清理。看了半天,弱引用也用不上啊。其实本来这个弱引用也只是一种防御性手段,始终记住在使用完一个线程本地变量后调用 remove 手动删除才是正经。

4. 结语

​ ThreadLocal 本质上还是空间换时间的思想,每个线程修改自己的副本,从而无锁并发执行。

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

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

相关文章

网络安全(黑客方向)从入门到进阶:核心攻击手法剖析与防御实战指南

前言 什么是网络安全 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 如何成为一名黑客 很多朋友在学习安全方面都会半路转行&#xff0…

开发了一个免费的批量视频语音字幕识别工具,核心点是可批量自动处理识别任务

这个批量识别功能是免费的、无限制的、可批量使用的功能&#xff0c;可实现音频、视频文件语音识别转txt文本、srt字幕&#xff0c;主要是能批量执行识别任务&#xff0c;不用手动一个个去识别&#xff0c;这是与其他语音识别软件的最大的区别&#xff0c;而且可同时处理视频和…

炸裂汇总!2025收官硬核干货:380+页深度拆解RAG/Agent/MCP等9大核心,建议熬夜研读!

作为一名在AI一线滚打多年的老兵&#xff0c;我直白告诉你们&#xff1a;市面上真正能从底层原理直通生产落地的AI工程资料&#xff0c;凤毛麟角。今天这份《The AI Engineering Guidebook&#xff08;2025 Edition&#xff09;》&#xff0c;就是其中绝对的顶尖存在——384页硬…

Java小白求职者在互联网大厂面试:从Spring Boot到微服务的技术探索

场景&#xff1a;互联网大厂面试 在某个阳光明媚的早晨&#xff0c;小白程序员“超好吃”来到了知名互联网大厂进行他的Java开发工程师面试。面试官是一位经验丰富且略显严肃的高级工程师。 第一轮提问&#xff1a;Java与Spring Boot基础 面试官&#xff1a;欢迎你&#xff0c;…

重塑未来安全格局的五大前沿技术:从AI安全到零信任的深度解读

目前信息安全领域&#xff08;不限于技术层面&#xff09;有哪些前沿的研究方向&#xff0c;代表人物有哪些&#xff1f;有哪些新的研究成果&#xff1f;以及从哪些地方可以获得这些咨询&#xff1f; 我在做 system 方向的安全研究&#xff0c;最近发现其实中美两国都在 TEE (…

【2026年最新】有关漏洞挖掘的一些总结,新手小白网络安全入门必看的经验教训!

时隔一年多以后再次看本文&#xff0c;依然给我一些启发&#xff0c;尤其是经过一定量的实践以后&#xff0c;发现信息收集真乃漏洞挖掘(渗透测试)的本质&#xff0c;这里再次回顾一下本文&#xff0c;尤其是里面如何评估一个项目(目标)的难度&#xff0c;值得学习与借鉴&#…

边缘模型增量微调实战

&#x1f493; 博客主页&#xff1a;借口的CSDN主页 ⏩ 文章专栏&#xff1a;《热点资讯》 边缘模型增量微调实战&#xff1a;从理论到边缘设备的高效部署目录边缘模型增量微调实战&#xff1a;从理论到边缘设备的高效部署 引言&#xff1a;边缘智能的必然选择 一、现在时&…

新中地学员转行学GIS开发原因盘点①

你有没有过那种时刻&#xff1a;明明已经很努力了&#xff0c;结果却不尽如人意&#xff1f; 比如考研失利、求职被拒&#xff0c;甚至开始怀疑自己选的专业到底适不适合…… 其实很多人都经历过这种“卡住”的瞬间&#xff0c;但有些人没有停下&#xff0c;而是悄悄换了赛道…

构建企业级安全防线:盘点网络安全防范的核心技术及其实战应用体系

伴随着互联网的发展&#xff0c;它已经成为我们生活中不可或缺的存在&#xff0c;无论是个人还是企业&#xff0c;都离不开互联网。正因为互联网得到了重视&#xff0c;网络安全问题也随之加剧&#xff0c;给我们的信息安全造成严重威胁&#xff0c;而想要有效规避这些风险&…

FileImgSwap 文图变文件藏到图片是一款可以把文件与 PNG 图像进行互转的工具

大家好&#xff0c;我是大飞哥。平时传敏感文件、存私密资料总怕泄露&#xff0c;或者想把文件藏起来不显眼&#xff0c;所以我搞了这款“FileImgSwap文图变文件藏到图片工具”&#xff0c;能把任意文件藏进PNG图里&#xff0c;还能还原提取&#xff0c;加了加密功能&#xff0…

2026最新流出!6款免费AI写论文工具,1天5万字还带真实参考文献!

距离DDL只剩72小时&#xff1f;别焦虑&#xff01;这篇深夜急救指南&#xff0c;为你揭秘6款能“一键救命”的AI论文神器&#xff0c;最快10分钟生成万字初稿&#xff0c;真实文献引用、自动降重、图表公式一键生成&#xff0c;让你在截止日期前优雅上岸&#xff01; 深夜的图书…

全网最全8个AI论文工具,本科生轻松搞定论文格式!

全网最全8个AI论文工具&#xff0c;本科生轻松搞定论文格式&#xff01; 论文写作的“神器”正在改变你的学习方式 对于许多本科生来说&#xff0c;撰写论文是一项既复杂又令人头疼的任务。从选题、收集资料到撰写初稿、格式调整&#xff0c;每一个环节都可能成为阻碍。而如今&…

‌高并发系统测试案例解析

在当今数字化时代&#xff0c;高并发系统&#xff08;如电商平台、社交媒体或金融服务&#xff09;已成为业务核心&#xff0c;但高用户负载下的性能问题频发。作为软件测试从业者&#xff0c;理解和测试这些系统至关重要。本文通过解析三个真实案例&#xff0c;探讨测试策略、…

学术生产力七重奏:当Paperzz领衔六大AI写作引擎,毕业论文从“卡壳”走向“丝滑交付”

Paperzz-AI官网免费论文查重复率AIGC检测/开题报告/文献综述/论文初稿 paperzz - 毕业论文-AIGC论文检测-AI智能降重-ai智能写作https://www.paperzz.cc/dissertation ——不是工具堆砌&#xff0c;而是一套可落地的“智能写作操作系统” 前言&#xff1a;2026年&#xff0c;…

图片格式转换与尺寸批量大师 支持ICO PNG JPG等八种格式 批量转换与自定义尺寸

大家好&#xff0c;我是大飞哥。平时做设计、剪视频或者整理素材&#xff0c;总得批量改图片格式、调尺寸——要么是做PPT要统一成JPG&#xff0c;要么是做图标要转ICO&#xff0c;之前用在线工具要么限速&#xff0c;要么一次只能传几张。这款“图片格式转换与尺寸批量大师”刚…

Python与USB 3.0用户态设备驱动:技术挑战与创新实践

Python与USB 3.0用户态设备驱动&#xff1a;技术挑战与创新实践摘要随着USB 3.0技术普及和Python在系统编程中的广泛应用&#xff0c;基于Python开发用户态USB 3.0设备驱动成为了一种创新趋势。本文深入探讨了在用户态环境下使用Python开发USB 3.0驱动的技术挑战、架构设计、性…

收藏!AI工程师分2派?一文分清传统算法与大模型应用,小白转行必看

提到AI工程师&#xff0c;不少人第一反应就是“写代码、调模型的技术大牛”。但其实AI工程师圈子里藏着两大核心分支——传统算法工程师和AI大模型应用开发工程师。简单来说&#xff0c;前者负责“让模型变聪明”&#xff0c;后者专注“让聪明的模型落地能用”&#xff0c;两者…

2025年新中地转行数据:谁才是GIS开发的主力军专业?

这一期来到了转GIS开发最多的专业盘点。 今天我们来看下&#xff0c;在新中地报名学习GIS开发的同学中&#xff0c;专业出现最频繁的是哪个&#xff1f;下面是所有已登记专业学生的类型和专业占比。毫不意外地&#xff0c;2025年所有转GIS开发的同学中&#xff0c;地理信息科学…

Loomis Sayles隆重庆祝百年华诞,矢志不渝服务客户、铸就投资卓越

资产管理规模达4250亿美元i的投资管理公司Loomis, Sayles & Company隆重庆祝百年诞辰&#xff0c;彰显其秉承独立思考精神、始终致力于助力客户实现目标的坚定初心。Loomis Sayles成立于1926年1月&#xff0c;如今已发展成为一家为28个国家的1000多家机构客户提供服务的公司…

文件夹及文件目录提取器 - 高效管理支持按指定层级提取目录结构提取深度提取文件目录结构的专业工具

大家好&#xff0c;我是大飞哥。平时整理软件库、做素材分类的时候&#xff0c;最头疼的就是记不清文件夹里到底存了啥——尤其是几百个文件夹堆在一起&#xff0c;找个文件得翻半天。索性自己写了这个“文件夹目录提取器”&#xff0c;就是奔着把复杂的文件结构一键理清楚去的…