ThreadLocal

一、ThreadLocal 核心作用

线程本地变量

每个线程独有一份变量副本,线程之间完全隔离、互不干扰;

用来避免多线程共享变量竞争,代替加锁,提升并发效率。


二、实现原理

1. 核心归属关系

每个 Thread 对象 内部,自带一个:

1
ThreadLocal.ThreadLocalMap threadLocals;

不是 ThreadLocal 存数据

  • 数据存在当前线程自己的 ThreadLocalMap 里
  • ThreadLocal 只是操作入口、key
  • 每个线程各自存自己的,天然隔离

2. 存取流程

  • threadLocal.set(value)
    1. 获取当前线程 Thread
    2. 拿到线程内部的 ThreadLocalMap
    3. 当前 ThreadLocal 对象为 key,存入 value
  • threadLocal.get()
    1. 获取当前线程
    2. 拿到自己的 ThreadLocalMap
    3. 以当前 ThreadLocal 为 key,取值

核心结论

变量存在线程里,不是存在 ThreadLocal;

多线程各自一张独立小表,互相看不见。


三、ThreadLocalMap 结构

1. 底层结构

  • 自定义哈希表,数组 + 开放地址法(线性探测)
  • 不是 HashMap,没有链表、红黑树

2. 存储单元 Entry

1
2
3
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
}
  • key:ThreadLocal<?> → 弱引用 WeakReference
  • value:业务数据 → 强引用

3. 哈希冲突解决

HashMap = 链表 / 红黑树

ThreadLocalMap = 线性探测

哈希冲突时下标 + 1,向后找空位存放、查找。

4. 弱引用设计目的

key 使用弱引用:

当 ThreadLocal 引用外部强引用失效、被 GC 回收,

key 会自动被回收,防止 ThreadLocal 常驻内存。


四、内存泄漏 原因 + 解决方案

1. 泄漏根本原因

  1. Entrykey 是弱引用value 是强引用
  2. 当外部 ThreadLocal 引用置空、被 GC:
    • key 被回收 → key = null
    • value 依旧被 Entry 强引用吊着
  3. 只要线程不销毁(如线程池核心线程长期存活)
    • 废弃 value 永远无法 GC → 内存泄漏

2. 为何不把 value 也设为弱引用?

业务数据需要使用者自己控制生命周期,

如果 value 弱引用,会被随意 GC,业务数据丢失。

3. 如何避免内存泄漏

  1. 使用完必须手动 remove ()

    1
    2
    3
    4
    5
    6
    try{
    threadLocal.set(obj);
    // 业务逻辑
    }finally{
    threadLocal.remove();
    }
  2. 优先使用 static 修饰 ThreadLocal

    减少 ThreadLocal 对象频繁创建销毁。

  3. 线程池场景特别注意

    线程复用、长期不销毁,不 remove 必泄漏。

  4. JVM 被动清理

    扩容、rehash、get/set 时会主动清理 key=null 的脏 Entry

    但不保证 100% 及时,不能依赖自动清理


五、使用场景

  1. 线程隔离上下文

    用户信息、登录态、请求上下文,全链路透传(Spring 上下文)

  2. SimpleDateFormat / 日期工具类

    非线程安全类,不用加锁,每个线程单独一份实例

  3. 事务管理

    同一个线程多次操作,绑定同一个 Connection 数据库连接

  4. 分布式链路追踪

    traceId、spanId 线程内全局透传

  5. 参数透传

    避免多层方法冗余传参


六、注意事项

  1. 绝对不要在线程池忘记 remove,复用线程会导致脏数据 + 泄漏

  2. 不能存全局共享数据,只能存线程私有数据

  3. 禁止存大对象,长期常驻线程会占用堆内存

  4. InheritableThreadLocal 父子线程传递

    普通 ThreadLocal 子线程拿不到父线程数据;

    InheritableThreadLocal 支持父子线程数据继承。

极简核心总结(原理浓缩)

  1. 数据存在 Thread.threadLocals,每个线程独立 Map,天然隔离。
  2. ThreadLocalMap 采用数组 + 线性探测,Entry 的 key 弱引用、value 强引用。
  3. 内存泄漏根源:key 回收、value 强引用滞留 + 线程长期存活
  4. 最佳实践:**try-finally + remove()**。
  5. 核心价值:无锁实现线程隔离,解决非线程安全类复用、上下文透传。

ThreadLocal
http://hanqichuan.com/2026/04/16/java并发/ThreadLocal/
作者
韩启川
发布于
2026年4月16日
许可协议