ThreadLocal
一、ThreadLocal 核心作用
线程本地变量:
每个线程独有一份变量副本,线程之间完全隔离、互不干扰;
用来避免多线程共享变量竞争,代替加锁,提升并发效率。
二、实现原理
1. 核心归属关系
每个 Thread 对象 内部,自带一个:
1 | |
不是 ThreadLocal 存数据
- 数据存在当前线程自己的 ThreadLocalMap 里
- ThreadLocal 只是操作入口、key
- 每个线程各自存自己的,天然隔离
2. 存取流程
threadLocal.set(value)- 获取当前线程 Thread
- 拿到线程内部的 ThreadLocalMap
- 以当前 ThreadLocal 对象为 key,存入 value
threadLocal.get()- 获取当前线程
- 拿到自己的 ThreadLocalMap
- 以当前 ThreadLocal 为 key,取值
核心结论
变量存在线程里,不是存在 ThreadLocal;
多线程各自一张独立小表,互相看不见。
三、ThreadLocalMap 结构
1. 底层结构
- 自定义哈希表,数组 + 开放地址法(线性探测)
- 不是 HashMap,没有链表、红黑树
2. 存储单元 Entry
1 | |
- key:ThreadLocal<?> → 弱引用 WeakReference
- value:业务数据 → 强引用
3. 哈希冲突解决
HashMap = 链表 / 红黑树
ThreadLocalMap = 线性探测
哈希冲突时下标 + 1,向后找空位存放、查找。
4. 弱引用设计目的
key 使用弱引用:
当 ThreadLocal 引用外部强引用失效、被 GC 回收,
key 会自动被回收,防止 ThreadLocal 常驻内存。
四、内存泄漏 原因 + 解决方案
1. 泄漏根本原因
Entry中 key 是弱引用,value 是强引用- 当外部 ThreadLocal 引用置空、被 GC:
- key 被回收 → key = null
- value 依旧被 Entry 强引用吊着
- 只要线程不销毁(如线程池核心线程长期存活)
- 废弃 value 永远无法 GC → 内存泄漏
2. 为何不把 value 也设为弱引用?
业务数据需要使用者自己控制生命周期,
如果 value 弱引用,会被随意 GC,业务数据丢失。
3. 如何避免内存泄漏
使用完必须手动 remove ()
1
2
3
4
5
6try{
threadLocal.set(obj);
// 业务逻辑
}finally{
threadLocal.remove();
}优先使用 static 修饰 ThreadLocal
减少 ThreadLocal 对象频繁创建销毁。
线程池场景特别注意
线程复用、长期不销毁,不 remove 必泄漏。
JVM 被动清理
扩容、rehash、get/set 时会主动清理 key=null 的脏 Entry,
但不保证 100% 及时,不能依赖自动清理。
五、使用场景
线程隔离上下文
用户信息、登录态、请求上下文,全链路透传(Spring 上下文)
SimpleDateFormat / 日期工具类
非线程安全类,不用加锁,每个线程单独一份实例
事务管理
同一个线程多次操作,绑定同一个 Connection 数据库连接
分布式链路追踪
traceId、spanId 线程内全局透传
参数透传
避免多层方法冗余传参
六、注意事项
绝对不要在线程池忘记 remove,复用线程会导致脏数据 + 泄漏
不能存全局共享数据,只能存线程私有数据
禁止存大对象,长期常驻线程会占用堆内存
InheritableThreadLocal 父子线程传递
普通 ThreadLocal 子线程拿不到父线程数据;
InheritableThreadLocal 支持父子线程数据继承。
极简核心总结(原理浓缩)
- 数据存在 Thread.threadLocals,每个线程独立 Map,天然隔离。
- ThreadLocalMap 采用数组 + 线性探测,Entry 的 key 弱引用、value 强引用。
- 内存泄漏根源:key 回收、value 强引用滞留 + 线程长期存活。
- 最佳实践:**try-finally + remove()**。
- 核心价值:无锁实现线程隔离,解决非线程安全类复用、上下文透传。