ThreadLocal 的值存在各线程的 ThreadLocalMap 中,以当前 ThreadLocal 为 key(弱引用)、value 为强引用;需手动 remove() 避免因 key 被 GC 后 value 泄漏。

ThreadLocal 的核心不是“全局变量”,而是每个线程独享一份副本,靠的是 Thread 对象内部的 ThreadLocalMap。 它不解决共享数据同步问题,而是规避共享——让每个线程操作自己的变量,天然线程安全。
ThreadLocal 的 set() 是怎么存值的?
调用 threadLocal.set(value) 时,并不会把值存在 ThreadLocal 实例里,而是:
- 获取当前线程(
Thread.currentThread()) - 从该线程对象中拿到它的
threadLocals字段(类型是ThreadLocalMap) - 以当前 ThreadLocal 实例为 key,传入的 value 为 value,存入这个 map
注意:key 是弱引用(WeakReference),这是为避免内存泄漏埋下的伏笔。
get() 为什么能拿到“自己”的值?
调用 threadLocal.get() 时流程类似:
立即学习“Java免费学习笔记(深入)”;
- 拿到当前线程
- 取出它的
threadLocals(可能为 null,首次调用会触发初始化) - 在 map 中以当前 ThreadLocal 实例为 key 查找 entry
由于每个线程都有独立的 ThreadLocalMap,所以自然互不干扰。哪怕多个线程用同一个 ThreadLocal 对象,它们操作的其实是各自 map 中的不同槽位。
ThreadLocalMap 是个什么结构?
它不是 HashMap,而是一个定制的、基于开放寻址法的哈希表:
- 底层是
Entry[] table,Entry 继承自WeakReference,value 是强引用 - 没有链表或红黑树,冲突时线性探测(向后找下一个空位)
- 每次 get/set 都会顺带做“探测式清理”(expungeStaleEntries):扫描并移除 key 已被 GC 的 stale entry
这个设计兼顾了轻量和性能,但也意味着:如果线程长期运行(如线程池中的线程),又没手动 remove(),就可能因 key 被回收而留下 value 泄漏。
为什么必须手动 remove()?
因为 key 是弱引用,GC 可能随时回收 ThreadLocal 对象,但 value 还牢牢挂在 Entry 里。ThreadLocalMap 的清理是被动且不彻底的(只清理探测路径上的 stale entry)。常见场景如 Web 应用中用 ThreadLocal 存用户上下文,在 Filter 或 Interceptor 结束时务必调用 threadLocal.remove()。
不 remove 的后果:线程复用时,旧 value 堆积,引发内存泄漏(尤其 value 是大对象或持有外部引用时)。
基本上就这些。理解 ThreadLocal,关键不在“怎么用”,而在“值到底存在哪”和“谁负责清理”。











