跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表

目录

  1. ThreadLocal 的原理和实现
  2. ThreadLocal 为什么要使用弱引用和内存泄露问题
  • 💰 8折买阿里云服务器限时8折了解详情
Javajava

ThreadLocal 原理与内存泄漏机制详解

ThreadLocal 实现线程变量隔离,每个线程持有独立的 ThreadLocalMap。Key 为弱引用可避免 ThreadLocal 对象无法回收,但 Value 若未清理在线程池复用场景下会导致内存泄漏。使用时应确保在 finally 块中调用 remove() 方法及时释放资源。

星河入梦发布于 2020/9/29更新于 2026/4/211 浏览
ThreadLocal 原理与内存泄漏机制详解

ThreadLocal 的原理和实现

ThreadLocal 变量即线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。ThreadLocal 变量通常被 private static 修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

一个线程内可以存在多个 ThreadLocal 对象,所以其实是 ThreadLocal 内部维护了一个 Map,这个 Map 不是直接使用的 HashMap,而是 ThreadLocal 实现的一个叫做 ThreadLocalMap 的静态内部类。

而我们使用的 get()、set() 方法其实都是调用了这个 ThreadLocalMap 类对应的 get()、set() 方法。这个储值的 Map 并非 ThreadLocal 的成员变量,而是 java.lang.Thread 类的成员变量。ThreadLocalMap 实例是作为 java.lang.Thread 的成员变量存储的,每个线程有唯一的一个 threadLocalMap。

这个 map 以 ThreadLocal 对象为 key,'线程局部变量'为值,所以一个线程下可以保存多个'线程局部变量'。对 ThreadLocal 的操作,实际委托给当前 Thread,每个 Thread 都会有自己独立的 ThreadLocalMap 实例,存储的仓库是 Entry[] table;Entry 的 key 为 ThreadLocal,value 为存储内容;因此在并发环境下,对 ThreadLocal 的 set 或 get,不会有任何问题。

由于 Tomcat 线程池的原因,最初使用的'线程局部变量'保存的值,在下一次请求依然存在(同一个线程处理),这样每次请求都是在本线程中取值。所以在线程池的情况下,处理完成后主动调用该业务 ThreadLocal 的 remove() 方法,将'线程局部变量'清空,避免本线程下次处理的时候依然存在旧数据。

ThreadLocal 为什么要使用弱引用和内存泄露问题

在 ThreadLocal 中内存泄漏是指 ThreadLocalMap 中的 Entry 中的 key 为 null,而 value 不为 null。因为 key 为 null 导致 value 一直访问不到,而根据可达性分析导致在垃圾回收的时候进行可达性分析的时候,value 可达从而不会被回收掉,但是该 value 永远不能被访问到,这样就存在了内存泄漏。

如果 key 是强引用,那么发生 GC 时 ThreadLocalMap 还持有 ThreadLocal 的强引用,会导致 ThreadLocal 不会被回收,从而导致内存泄漏。弱引用 ThreadLocal 不会内存泄漏,对应的 value 在下一次 ThreadLocalMap 调用 set、get、remove 方法时被清除,这算是最优的解决方案。

Map 中的 key 为一个 threadlocal 实例。如果使用强引用,当 ThreadLocal 对象(假设为 ThreadLocal@123456)的引用被回收了,ThreadLocalMap 本身依然还持有 ThreadLocal@123456 的强引用,如果没有手动删除这个 key,则 ThreadLocal@123456 不会被回收,所以只要当前线程不消亡,ThreadLocalMap 引用的那些对象就不会被回收,可以认为这导致 Entry 内存泄漏。

如果使用弱引用,那指向 ThreadLocal@123456 对象的引用就两个:ThreadLocal 强引用和 ThreadLocalMap 中 Entry 的弱引用。一旦 ThreadLocal 强引用被回收,则指向 ThreadLocal@123456 的就只有弱引用了,在下次 gc 的时候,这个 ThreadLocal@123456 就会被回收。

虽然上述的弱引用解决了 key,也就是线程的 ThreadLocal 能及时被回收,但是 value 却依然存在内存泄漏的问题。当把 threadlocal 实例置为 null 以后,没有任何强引用指向 threadlocal 实例,所以 threadlocal 将会被 gc 回收。map 里面的 value 却没有被回收。而这块 value 永远不会被访问到了。

所以存在着内存泄露,因为存在一条从 current thread 连接过来的强引用。只有当前 thread 结束以后,current thread 就不会存在栈中,强引用断开,Current Thread, Map, value 将全部被 GC 回收。所以当线程的某个 localThread 使用完了,马上调用 threadlocal 的 remove 方法,就不会发生这种情况了。

另外其实只要这个线程对象及时被 gc 回收,这个内存泄露问题影响不大,但在 threadLocal 设为 null 到线程结束中间这段时间不会被回收的,就发生了我们认为的内存泄露。最要命的是线程对象不被回收的情况,这就发生了真正意义上的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用,就可能出现内存泄露。

  • 💰 8折买阿里云服务器限时8折购买
  • 🦞 5分钟部署阿里云小龙虾了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog

更多推荐文章

查看全部
  • 链表的基本概念与结构实现
  • Ubuntu 16.04 搭建 SVN 服务器指南
  • Android 传感器全解:注册监听与常用传感器应用
  • 分布式计算
  • 解决 JPA 中 new Date() 插入数据库时间差 8 小时问题
  • Rust 使用迭代器适配器简化代码实现
  • JavaScript 原子读和写操作详解
  • Struts2 接收请求参数:基本类型与复合类型

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

HQL 命名参数与 Query 接口的分页查询
  • Web.xml 中 Listener、Filter、Servlet 加载顺序详解
  • SpringBoot 接入 JaCoCo 实践:代码覆盖率统计
  • PyTorch 中 LSTM 模型参数详解
  • Java 实现网页内容转换为 MHT 文件格式代码
  • Python 调用豆包 API 示例代码详解
  • 微信支付接入密码输入后转圈失败问题排查
  • 电子招标采购商城系统优化传统采购与数字化升级
  • Java 企业电子招投标采购系统项目说明及功能架构
  • 企业电子招标采购管理系统功能与技术架构
  • Java 直播商城架构规划与常见营销模式解析
  • Mycat 实践指南:一致性 Hash 分片下的水平分表详解