跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

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

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

星河入梦发布于 2020/9/29更新于 2026/10/26101 浏览
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 到线程结束中间这段时间不会被回收的,就发生了我们认为的内存泄露。最要命的是线程对象不被回收的情况,这就发生了真正意义上的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用,就可能出现内存泄露。

目录

  1. ThreadLocal 的原理和实现
  2. ThreadLocal 为什么要使用弱引用和内存泄露问题

更多推荐文章

查看全部
  • C++ 智能指针完全指南:从原理到实战
  • Mac 重装系统及指定版本升级指南
  • Python ezdxf 库:DXF 文件处理入门与实战
  • 冷启动数据与多阶段训练在 DeepSeek 中的作用
  • OpenClaw:开源 AI 智能体框架架构、部署与安全分析
  • Raphael AI:基于 Flux 模型的在线 AI 图像生成工具
  • AI 赋能产品经理:提示词框架与工具实战指南
  • 基于 SpringBoot2+Vue3 的大学生科创项目在线管理系统
  • Stable Diffusion v1.5 部署指南:GPU 加速与 Web 服务启动
  • Web 项目自动化测试实战:从零搭建博客系统 UI 自动化框架
  • 人工智能嵌入(Embedding)概念详解与语义映射原理
  • WhisperLiveKit 本地部署实时语音转文字与说话人识别
  • Java Spring 框架初识与 Maven 项目创建指南
  • 动态规划:子数组与子串问题实战
  • tksheet完整指南:Python Tkinter表格组件的终极解决方案
  • LLaMA 3.1 模型部署与智能聊天机器人构建
  • 2025 前端复盘:框架内卷落幕,AI 重构生态与开发者破局
  • 基于 DeepSeek 的贪吃蛇游戏开发实战
  • 解密 Copilot:如何打造高效的 AI 原生应用
  • AI 辅助游戏开发:基于 DeepSeek 构建贪吃蛇实战

相关免费在线工具

  • 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