ThreadLocal
ThreadLocal
一、本质
- ThreadLocal 是线程内的全局变量:同一个线程中,任何地方都能拿到,不同线程之间隔离。
- 底层是 ThreadLocalMap:每个线程(
Thread)内部都有一个threadLocals字段,指向一个ThreadLocalMap。 - ThreadLocalMap 就是一个数组:用线性探测法解决哈希冲突,数组长度永远是 2 的幂。
二、内存泄漏(核心考点)
- Entry 对 key 是弱引用,对 value 是强引用:
key(ThreadLocal 对象)→ 弱引用,GC 时会回收value(你存的数据)→ 强引用,不会自动回收
- 泄漏原因:
local = null后,ThreadLocal 对象被 GC 回收,key变null- 但
Entry还在数组里,value还被强引用着 → 永远无法回收
- Entry 不会被自动回收:因为
table数组强引用着它,线程不销毁,Entry 就在。
三、正确使用
- 必须调用
remove():finally块里执行,主动删除 Entry,释放 value。 local = null没用:它只断开你对 ThreadLocal 对象的引用,不清理线程内部的 Entry。99% 场景不需要。- 静态 ThreadLocal 绝不能赋
null:否则下次再用就 NPE。
四、一句话记住
ThreadLocal 是线程内的全局变量,用完必须
remove(),否则 value 会内存泄漏。local = null没用。
五、面试高频题
| 问题 | 答案 |
|---|---|
| ThreadLocal 底层是什么? | 每个线程有个 ThreadLocalMap(Entry 数组) |
| 为什么用弱引用? | 保证 ThreadLocal 对象能被回收 |
| 为什么还会内存泄漏? | value 是强引用,且 Entry 不会被自动清理 |
| 怎么避免? | 用 remove() |
| 和局部变量有什么区别? | 局部变量作用域是方法,ThreadLocal 作用域是整个线程 |
Netty 中的 FastThreadLocal和ThreadLocal的区别
它们在设计理念上完全一样,都是线程内的全局变量。区别在于实现方式和性能优化。
FastThreadLocal 是 Netty 对 JDK ThreadLocal 的高性能替代品:用“数组+固定索引”代替“哈希表+线性探测”,用显式清理代替弱引用,以此换取更高的访问速度和更可控的内存安全。
FastThreadLocal 不是 “即插即用” 的通用替代品。只有当你的线程是 FastThreadLocalThread(或其子类)时,才能享受到性能优势。Netty 的 DefaultThreadFactory 默认创建的就是 FastThreadLocalThread
Netty 中 NioEventLoopGroup 管理的 Boss 和 Worker 线程,其底层线程实例默认都是 FastThreadLocalThread。
FastThreadLocalThread里面的实际变量InternalThreadLocalMap
核心原理:InternalThreadLocalMap 的“双重身份”
InternalThreadLocalMap 的设计非常精妙,它同时扮演了两个角色:
FastThreadLocal的专用存储:当业务代码调用FastThreadLocal.get()时,实际上是从这个 Map 中快速读取数据。- 一个具体的
ThreadLocal变量:为了让每个线程都拥有自己独立的InternalThreadLocalMap实例,Netty 借助了一个普通的 JDKThreadLocal来做“容器”。
让我们看看 InternalThreadLocalMap 的静态 get() 方法是怎么实现的:
java
// 这是 InternalThreadLocalMap 类的关键代码
public static InternalThreadLocalMap get() {
Thread thread = Thread.currentThread();
if (thread instanceof FastThreadLocalThread) {
// 🚀 快车道:直接从 FastThreadLocalThread 的成员变量中获取数组
return fastGet((FastThreadLocalThread) thread);
} else {
// 🐌 慢车道:从 JDK 的 ThreadLocal 中获取(回退方案)
return slowGet();
}
}
路线一:高速快车道 (当线程是 FastThreadLocalThread)
这是最理想的情况。FastThreadLocalThread 类本身就持有一个 InternalThreadLocalMap 的引用。fastGet() 方法会直接从线程对象的成员变量中取出这个 Map,速度极快,几乎没有额外开销。
路线二:兼容慢车道 (当线程是普通 Thread)
如果你的环境只能用普通线程(比如 Tomcat 的线程池),slowGet() 方法会通过一个 JDK 的 ThreadLocal 来存放这个 InternalThreadLocalMap。这就是我们之前提到的“回退”机制。虽然它依然能工作,但会比快车道多一次 ThreadLocal 的查找开销。
💎 总结:一张图看懂全貌
- 执行
FastThreadLocal.get() - ↓
- 先拿到
InternalThreadLocalMap- 判断当前线程类型
- 是
FastThreadLocalThread?- 是的:直接从线程对象中“伸手”拿到
InternalThreadLocalMap(内部维护一个Object[]数组) - 不是:通过一个普通的
ThreadLocal来获取InternalThreadLocalMap(这是降级兼容方案)
- 是的:直接从线程对象中“伸手”拿到
- 是
- 判断当前线程类型
- ↓
- 拿到
InternalThreadLocalMap后,根据FastThreadLocal创建时分配的唯一index,直接读写其内部的Object[]数组
简单来说,InternalThreadLocalMap 就是那个最终存放数据的“柜子”,而 FastThreadLocalThread 身上有个“挂钩”可以随时高效地挂着这个柜子。
所以,你的观察非常准确。正是因为这层设计,才必须使用 FastThreadLocalThread。否则,Netty 就只能通过 JDK 的 ThreadLocal 来间接获取那个“柜子”,性能自然就下降了。
浙公网安备 33010602011771号