ThreadLocalMap采用斐波那契散列(魔数0x61c88647)生成不可变hash值,通过线性探测解决冲突,key为弱引用以支持GC清理stale entry,索引计算为hash&(len-1),扩容时翻倍并重哈希。

Java 中 ThreadLocal 本身不直接计算 hash,真正负责哈希计算的是其内部静态类 ThreadLocalMap。这个 map 的 key 是 ThreadLocal 实例,value 是用户存入的线程局部变量。它的 hash 计算方式**不是基于 ThreadLocal 的 hashCode()**,而是采用一种**固定步长的线性探测法 + 预设初始哈希值**的设计,核心目的是避免哈希冲突堆积、提升访问效率,并适配弱引用 key 的清理机制。
hash 计算:不是调用 hashCode(),而是用 probe 值线性递推
ThreadLocalMap 不重写或依赖 ThreadLocal 的 hashCode() 方法。它使用一个叫 threadLocalHashCode 的静态字段(类型为 AtomicInteger),每次新建 ThreadLocal 实例时,通过:
nextHashCode.getAndAdd(HASH_INCREMENT)- 其中
HASH_INCREMENT = 0x61c88647(十进制约 1640531527)
这个 magic number 是黄金分割比例 (√5−1)/2 ≈ 0.618 的近似整数倍,能保证在 2 的幂次容量下,不同 ThreadLocal 的初始 hash 值尽可能均匀分布,减少碰撞概率。
数组索引定位:hash & (len - 1),但探测过程是线性的
虽然底层 Entry[] table 容量是 2 的幂(如默认 16),索引计算形式上是:
立即学习“Java免费学习笔记(深入)”;
int i = key.threadLocalHashCode & (len - 1);
但这只是**初始槽位(first probe)**。一旦发生冲突(该位置已有非空 Entry 或 key 已被回收),ThreadLocalMap 不采用链地址法,而是按固定步长(step = 1)向后线性探测:
- 检查
table[i]→ 若为空或 key 为 null,尝试插入/替换 - 若 key 相等(
==判断,非equals),则命中,更新 value - 否则
i = nextIndex(i, len),即i = (i + 1) & (len - 1),继续循环 - 直到找到空位、遇到相同 key,或绕回起点
为什么不用 Object.hashCode()?关键在弱引用与清理逻辑
ThreadLocalMap.Entry 继承自 WeakReference<threadlocal></threadlocal>,key 是弱引用。这意味着:
- GC 可随时回收无强引用的
ThreadLocal实例 - 对应 Entry 的 key 变成
null,但 Entry 本身还留在 table 中(“stale entry”) - 后续 get/set 操作会触发探测过程中的清理逻辑:跳过 stale entry,顺便把它们从 table 中清除
如果依赖 Object.hashCode(),而该方法可能随对象状态变化(尽管 ThreadLocal 未重写,但规范不保证稳定),会导致同一实例在不同时间算出不同 hash,破坏 map 正确性。而 threadLocalHashCode 在构造时就确定且不可变,完全可控。
扩容与 rehash:不靠 hash 优化,靠探测+清理+翻倍
ThreadLocalMap 没有传统 HashMap 的红黑树或复杂 rehash 策略。当元素数量超过阈值(len * 2/3)时:
- 创建新数组,容量翻倍(2 的幂)
- 遍历旧表,对每个非 null、非 stale 的 Entry,重新计算初始索引
i = key.threadLocalHashCode & (newLen - 1) - 再用线性探测插入新表 —— 这个过程也顺带清理所有 stale entry
所以它的“哈希质量”不靠二次散列或扰动函数,而靠初始 hash 的均匀性 + 探测时的清理能力 + 翻倍扩容后的空间冗余来维持性能。


















