1. 引言ThreadLocal 是一个线程本地变量提供了set()、get()和remove()三个核心方法。当创建一个 ThreadLocal 变量后每个线程在访问该变量时都会拷贝一份独立副本存储在自己的线程空间中从而实现线程隔离避免多线程间的数据竞争问题。2. 典型使用场景线程池场景避免线程复用带来的数据串扰每个线程使用自己的 ThreadLocal 存储独有数据。Web / 应用上下文在请求链路上存储用户 ID、租户信息等上下文方便任意位置获取。3. 实现原理3.1 创建 ThreadLocal 变量publicstaticThreadLocalStringlocalVariablenewThreadLocal();3.2 ThreadLocalMap 内部结构调用set()方法后首先会获取当前线程Thread然后在其内部创建一个ThreadLocalMap。每个线程只有一个ThreadLocalMap第一次调用set()时创建后续直接使用。虽然名称带有 “Map”但它并非真正的java.util.Map而是内部维护的一个Entry数组采用 Key‑Value 的形式存储数据。Key可简单理解为 ThreadLocal 对象本身但实际上是一个弱引用。Value用户存储的实际数据。每个Entry都是一个完整的对象同时持有 Key 和 Value。每个 ThreadLocal 对象只能存储一个值当前线程中所有的 ThreadLocal 变量都作为Entry存储在同一个ThreadLocalMap中。3.3 弱引用与 GC弱引用意味着每次 GC垃圾回收发生时无论内存是否充裕只要没有其他强引用指向对象该弱引用对象就会被回收。4. 内存泄露问题及解决由于 Key 是弱引用当 GC 发生时ThreadLocal 的弱引用 Key 会被回收但Value 仍强关联着 Entry导致 Value 无法被释放从而引发内存泄露。解决方法使用完 ThreadLocal 后必须及时调用remove()方法显式销毁 Entry避免 Value 残留。为什么不把 Key 设计为强引用若 Key 是强引用当外部 ThreadLocal 引用被销毁后Entry 中的 Key 仍然强引用着 ThreadLocal 对象导致 ThreadLocal 无法被 GC 回收同样造成内存泄漏。因此弱引用是精心设计后的折中方案但必须配合remove()保证 Value 被清除。5. ThreadLocalMap 如何解决哈希冲突不同于HashMap的“链地址法”链表 / 红黑树ThreadLocalMap 采用开放定址法中的线性探测来解决冲突根据 ThreadLocal 的哈希值计算目标槽位。若该槽位已有 Entry则向下索引递增寻找下一个可用槽位直到找到空位存放。6. ThreadLocalMap 扩容机制ThreadLocalMap 的扩容分为两步Rehash当占用空间超过2/3时触发主要任务是清理过期stale的 Entry腾出空间。Resize清理后若占用仍超过3/4阈值则执行真正扩容将容量扩大为原来的两倍并将所有 Entry 重新散列到新数组。这种设计既避免了频繁扩容的性能开销也兼顾了过期键带来的空间浪费。7. 总结ThreadLocal 为解决线程间数据隔离而生每个线程持有变量的独立副本。内部通过 ThreadLocalMap 管理数据Key 使用弱引用 开放定址法处理冲突。弱引用设计是为了防止 ThreadLocal 对象本身无法回收但必须手动remove()来防止 Value 泄露。扩容采用“先清理、再倍增”的策略兼顾性能与空间。