ThreadLocal

ThreadLocal

一、本质

  1. ThreadLocal 是线程内的全局变量:同一个线程中,任何地方都能拿到,不同线程之间隔离。
  2. 底层是 ThreadLocalMap:每个线程(Thread)内部都有一个 threadLocals 字段,指向一个 ThreadLocalMap
  3. ThreadLocalMap 就是一个数组:用线性探测法解决哈希冲突,数组长度永远是 2 的幂。

二、内存泄漏(核心考点)

  1. Entry 对 key 是弱引用,对 value 是强引用
    • key(ThreadLocal 对象)→ 弱引用,GC 时会回收
    • value(你存的数据)→ 强引用,不会自动回收
  2. 泄漏原因
    • local = null 后,ThreadLocal 对象被 GC 回收,keynull
    • Entry 还在数组里,value 还被强引用着 → 永远无法回收
  3. Entry 不会被自动回收:因为 table 数组强引用着它,线程不销毁,Entry 就在。

三、正确使用

  1. 必须调用 remove()finally 块里执行,主动删除 Entry,释放 value。
  2. local = null 没用:它只断开你对 ThreadLocal 对象的引用,不清理线程内部的 Entry。99% 场景不需要。
  3. 静态 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 管理的 BossWorker 线程,其底层线程实例默认都是 FastThreadLocalThread

FastThreadLocalThread里面的实际变量InternalThreadLocalMap

核心原理:InternalThreadLocalMap 的“双重身份”

InternalThreadLocalMap 的设计非常精妙,它同时扮演了两个角色:

  1. FastThreadLocal 的专用存储:当业务代码调用 FastThreadLocal.get() 时,实际上是从这个 Map 中快速读取数据。
  2. 一个具体的 ThreadLocal 变量:为了让每个线程都拥有自己独立的 InternalThreadLocalMap 实例,Netty 借助了一个普通的 JDK ThreadLocal 来做“容器”。

让我们看看 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 来间接获取那个“柜子”,性能自然就下降了。

posted @ 2026-04-25 22:36  deyang  阅读(4)  评论(0)    收藏  举报