ThreadLocal 线程本地变量,对象的存储是在线程开始时分配,线程结束时回收,每个线程有该对象自己的实例,线程之间隔离。
这种对象的链接性(linkage)可以是静态的也可是外部的。但ThreadLocal变量通常是私有,静态。
原因: 防止每一个线程或对象重复创建 ThreadLocal实例。ThreadLocal本质上只是作为访问ThreadLocalMap的 Key,全局只需要一份静态 Key 即可。
ThreadLocal使用场景:
在并发场景下的多线程修改共享变量时,会出现线程安全问题
这里可以用锁实现

但是当并发量增大的时候,锁显然不是一个好方法。

在线程内部,共享上下文数据,避免多层方法不停传递参数;并且天然做到线程之间数据隔离。
ThreadLocal又是如何实现县城隔离的呢?
数据部署存在ThreadLocal对象上,而是存在每个Thread线程对象内部的 ThreadLocalMap 每个线程都拥有自己独立的Map,所以能天然隔离。
哈希冲突问题
ThreadLocal解决哈希冲突问题并没有使用 HashMap 的链表/红黑树 解决哈希冲突 而是线性探测法:
当计算出的数组下标已经占用时,ThreadLocalMap 会依次查找下一个位置(i + 1),直到找到空位或待更新的节点。
Key 为什么设计成弱引用?
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value; // value 是强引用!
Entry(ThreadLocal<?> k, Object v) {
super(k); // key 是弱引用!
value = v;
}
}
如果Key为强引用:
即使ThroadLocal为空,Entry依然被强引用,导致ThreadLocal对象无法在GC时被回收,引发内存泄漏
如果Key为弱引用:
当业务代码的ThreadLocal为空,下次JVM进行GC时,无视弱引用,ThreadLocal对象会被立即回收。
Value为什么不是弱引用?
如果Value是弱引用,可能代码里还没有用Value(没有强引用指向Value)gc之后就会错误的把Value回收
这样后面就用不了了,所以Value必须是强引用。所以Value是无法自动回收的,导致Value内存泄漏,所以用完ThreadLocal之后必须调用remove方法,断开Value引用。
内存泄漏
为防止内存泄漏 必须要进行remove回收
public void handleRequest(User user) {
userContextThreadLocal.set(user);
try {
// 执行核心业务逻辑
doBusiness();
} finally {
// 确保在请求结束时彻底清除线程本地变量,规避内存泄漏与线程复用带来的数据脏读
userContextThreadLocal.remove();
}
}
既然忘记 remove() 都会导致内存泄漏,为什么不全用强引用并强制要求开发者 remove() ??
若外部对 ThreadLocal 的引用切断,但 ThreadLocalMap 仍持强引用,Key 永远不为 null。ThreadLocalMap 无法判断数据是否废弃,100% 造成永久泄漏。
既然 Key 变成 null 会被清理,为什么必须显式 remove()?
JDK 的脏节点清理(expungeStaleEntry)不会主动定时运行,必须依赖当前线程下一次调用 get()、set() 或 remove() 顺带触发。
如果该线程随后归还给线程池且长时间无任务,或者后续任务不再操作该 ThreadLocal,Value 将一直停留在内存中无法释放。
而且下一次使用时,若未设置新值而直接 get(),会直接读取到上一个对象的敏感数据。
手动 remove() 的第一要义是重置线程上下文,确保数据安全隔离。