当前位置: 首页 > news >正文

ThreadLocal 原理与内存泄漏

ThreadLocal很容易被一句话讲浅:它是线程本地变量。

这句话没错,但不够。面试里真正要讲清楚的是:

  1. 它解决什么问题。
  2. 值到底存在哪里。
  3. 为什么会有内存泄漏风险。
  4. 为什么用完要remove()

ThreadLocal 解决什么问题

ThreadLocal的核心作用是:让每个线程都有自己独立的一份变量副本,避免多个线程争用同一份共享变量。

比如 JDBC 场景里,每个线程可以把自己的Connection放到 ThreadLocal 中。这样线程 A 不会误用或关闭线程 B 的连接。

ThreadLocal

线程 A

线程 B

线程 C

资源副本 A

资源副本 B

资源副本 C

它也常用于保存线程上下文,比如登录用户信息、traceId、租户信息等。

但注意,ThreadLocal不是用来解决所有线程安全问题的。它解决的是“每个线程各用各的”这一类问题。如果多个线程本来就需要共同修改同一份数据,ThreadLocal不适合。

基本用法

PPT 里的例子很直接:

staticThreadLocal<String>threadLocal=newThreadLocal<>();publicstaticvoidmain(String[]args){newThread(()->{Stringname=Thread.currentThread().getName();threadLocal.set("itcast");print(name);System.out.println(name+"-after remove : "+threadLocal.get());},"t1").start();newThread(()->{Stringname=Thread.currentThread().getName();threadLocal.set("itheima");print(name);System.out.println(name+"-after remove : "+threadLocal.get());},"t2").start();}staticvoidprint(Stringstr){System.out.println(str+" :"+threadLocal.get());threadLocal.remove();}

常用方法就三个:

方法作用
set(value)设置当前线程对应的值
get()获取当前线程对应的值
remove()移除当前线程对应的值

值到底存在 ThreadLocal 里吗

这是关键点。

值不是存在ThreadLocal对象自己里面,而是存在当前线程对象的ThreadLocalMap里。

每个Thread内部都有一个ThreadLocalMap。这个 Map 的 key 是ThreadLocal对象,value 是你放进去的线程本地值。

作为 key

Thread-1

ThreadLocalMap

Entry

key: ThreadLocal 弱引用

value: 线程本地值 强引用

ThreadLocal 对象

所以threadLocal.set(value)的真实含义是:

ThreadLocal自己作为 key,把 value 放进当前线程的ThreadLocalMap

set 方法大致怎么走

简化一下源码:

publicvoidset(Tvalue){Threadt=Thread.currentThread();ThreadLocalMapmap=getMap(t);if(map!=null){map.set(this,value);}else{createMap(t,value);}}

流程图如下:

存在

不存在

ThreadLocal.set(value)

获取当前线程 Thread

获取线程的 ThreadLocalMap

Map 是否存在

以当前 ThreadLocal 为 key 存 value

创建 ThreadLocalMap

get()也是类似逻辑:

  1. 获取当前线程。
  2. 找到当前线程的ThreadLocalMap
  3. 用当前ThreadLocal作为 key 查 Entry。
  4. 返回 Entry 里的 value。

为什么会内存泄漏

ThreadLocalMap.Entry的 key 是弱引用。

源码形态大概是:

staticclassEntryextendsWeakReference<ThreadLocal<?>>{Objectvalue;Entry(ThreadLocal<?>k,Objectv){super(k);value=v;}}

弱引用的特点是:GC 发现后就可以回收它指向的对象。

问题来了:

如果ThreadLocal对象没有外部强引用了,GC 会把 key 回收掉。于是 Entry 变成:

key = null value = 业务对象

value 还是强引用,仍然被当前线程的ThreadLocalMap持有。

如果这个线程是线程池里的长期存活线程,那 value 也可能长期无法释放。这就是内存泄漏风险。

线程池工作线程长期存活

ThreadLocalMap 长期存在

Entry

key: ThreadLocal 弱引用

value: 业务对象 强引用

ThreadLocal 外部强引用消失

GC 回收 key

key 变成 null

value 仍被 Entry 强引用

内存泄漏风险

这里还有一个面试里常见的追问:

为什么 key 要设计成弱引用?

如果 key 是强引用,ThreadLocalMap会一直强引用 ThreadLocal。即使业务代码不再使用这个 ThreadLocal,它也无法被回收。弱引用至少能让 key 被回收,后续set/get/remove时,Map 有机会清理这些 stale entry。

但这不代表你可以不remove()。因为清理时机不一定马上发生。

为什么用完必须 remove

线程池场景下,线程会被复用。

如果请求 A 在 ThreadLocal 里放了用户 A 的信息,但没有清理,线程回到线程池后又被请求 B 复用。请求 B 可能读到请求 A 的上下文。

这已经不是内存泄漏,而是数据串了。

请求 BThreadLocalMap线程池线程请求 A请求 BThreadLocalMap线程池线程请求 A使用线程set 用户 A请求结束但未 remove复用同一线程get 读到旧值用户 A

正确写法是放在finally

try{USER_CONTEXT.set(user);// do business}finally{USER_CONTEXT.remove();}

只要是 Web 请求、线程池任务、异步任务里的 ThreadLocal,都应该养成这个习惯。

ThreadLocal 和 synchronized 的区别

这两个经常被放在一起问,但它们解决问题的方式完全不同。

对比点synchronizedThreadLocal
思路多线程共享同一份数据时,加锁排队访问每个线程各保存一份数据
解决的问题共享变量并发修改线程隔离、线程上下文
数据是否共享共享不共享
典型场景计数、库存、临界区用户上下文、连接对象、traceId

一句话:

synchronized 是让大家排队用同一份东西,ThreadLocal 是给每个线程各发一份东西。

面试怎么答

可以这样回答:

ThreadLocal用来实现线程隔离。它会让每个线程拥有自己独立的变量副本,避免多个线程争用同一个对象,也可以在线程内共享上下文,比如用户信息、traceId、数据库连接等。

它的值不是存储在ThreadLocal对象本身,而是存储在当前线程的ThreadLocalMap中。ThreadLocalMap的 key 是 ThreadLocal,value 是线程本地变量。

内存泄漏风险来自ThreadLocalMap.Entry的 key 是弱引用,value 是强引用。当 ThreadLocal 没有外部强引用时,key 可能被 GC 回收成 null,但 value 仍然被线程持有。如果线程是线程池里的长期存活线程,value 可能长期无法释放。

所以使用 ThreadLocal 后,尤其在线程池和 Web 请求场景中,一定要在finally里调用remove(),避免内存泄漏和上下文串数据。

http://www.rkmt.cn/news/1475111.html

相关文章:

  • AI Agent时代来临:智能体正在重新定义软件与互联网
  • 新手也能看懂的IDA反汇编实战:从APK里揪出SO库,一步步破解EasySo的CheckString函数
  • 无需本地折腾,用快马平台5分钟搭建claude code云端原型验证工具
  • 数据安全与灾备技术
  • CORDIC算法:用移位与加减实现硬件高效三角函数计算
  • 职教高考优选|合肥理工 2026 官方咨询号码更新发布 - cc江江
  • 手把手教你:用qemu-img和vmkfstools搞定KVM虚拟机迁移到ESXi 6.7/7.0(附dracut启动失败修复)
  • AI科技热点日报 | 2026年6月6日
  • 裸眼3D MP4核心技术解析:从DSP算法到定制屏幕的工程实践
  • SimpleMem:长期记忆不是存得更多,而是让每个 token 更有信息密度
  • 2026中检战略合作门店|青岛禹竞名奢汇,依托上金所大盘实时计价结算 - 奢侈品交易观察员
  • 2026重庆财税咨询机构最新排行:4家合规服务商深度对比 - 奔跑123
  • D类功放核心原理与工程实践:从PWM调制到电路调试全解析
  • 别再为SolidWorks模型发愁了!用C# WinForm + SharpGL打造轻量级3D查看器(附完整源码)
  • 2026 广州黄埔财税 TOP5实测盘点,高新申报、公司注册一站式测评 - 资讯综合站
  • 1Remote终极指南:如何一站式管理所有远程连接协议
  • 从命令行到图形界面:N_m3u8DL-CLI-SimpleG如何让视频下载变得触手可及
  • 构建企业级Web安全防护:基于Coraza WAF的高性能解决方案
  • 重庆阿尔汉思木业:品质看得见 - 速递信息
  • 告别坐标转换焦虑:手把手教你用C++实现高斯与经纬度互转(附完整代码)
  • 如何永久免费使用IDM:一键激活脚本终极教程
  • 大连黄金回收门店排行前三盘点 正规老店上门变现无套路 - 奢侈品回收评测
  • 别再乱试了!手把手教你用`torch.cuda.get_arch_list()`精准诊断PyTorch与显卡算力不匹配问题
  • 东莞6家黄金回收怎么挑?六大维度对照测评,“禹竞名奢汇”成行业参照标准 - 奢侈品交易观察员
  • Python Matter Server:构建本地化智能家居控制中枢的完整指南
  • 厦门黄金回收深度测评|实测甄选靠谱门店,告别隐形扣费陷阱 - 奢侈品回收评测
  • 2026济南奢侈品回收指南:新手小白必看,添价收资质齐全办事高效 - 薛定谔的梨花猫
  • 用快马快速原型前端面试题:手写深拷贝的交互式演示
  • 2026 广州黄埔注册公司避坑指南,开发区创业者远离财税隐形消费 - 资讯综合站
  • 2026广州黄金回收,合扬:用实力把“最”字坐实,夺冠领先 - 开心测评