ThreadLocal采用线性探测解决哈希冲突:当计算位置已有不匹配key的Entry时,通过(i+1)%len循环探测至null或匹配key处,期间清理key为null的stale entry并重哈希有效Entry,兼顾性能、缓存友好性与内存安全。

ThreadLocal 的哈希冲突不靠链表或红黑树,而是用线性探测——简单、直接、专为线程私有场景优化。
线性探测怎么被触发
它不是一上来就探测,而是在「计算出的数组位置已有 Entry,且 key 不匹配」时才启动。核心逻辑藏在 set() 方法的 for 循环里:
- 初始索引 i = key.threadLocalHashCode & (len - 1),这是位运算哈希,保证落在有效范围内
- 只要 tab[i] 不为 null,就进入循环;第一次是检查,从第二次起才是真正的“探测”
- 每次调用 nextIndex(i, len),实际就是 (i + 1) % len,即加一、到尾回绕
- 循环持续到遇到 null 槽位(可插入)或 key 相等的 Entry(覆盖 value)为止
探测过程中顺手清理过时条目(stale entry)
线性探测不只是找位置,还承担内存安全职责。一旦碰到 key == null 的 Entry(ThreadLocal 被 GC 回收后留下的“僵尸”),就会立即调用 expungeStaleEntry(i):
统一邮件管理技能,用于读取、发送、回复、转发邮件及管理草稿箱和文件夹。用户提及「读邮件」「发邮件」「回复」「草稿箱」「未读邮件」「收件箱」「邮箱」等邮件相关操作时使用此技能。基于 Python 脚本操作任意 IMAP/SMTP 邮箱。
- 清空该槽位,并向后扫描连续非空槽,把所有 key == null 的 Entry 全部清理
- 将还能用的 Entry 按新哈希值重新定位,尽量挪到更靠近其理想位置的地方,缩短后续探测距离
- 清理后若发现已有相同 key 的 Entry(比如刚被重哈希挪过来的),就直接覆盖 value,不再继续往后找
- 这个清理只发生在当前探测路径上,不全表扫描,也不递归到底,但能有效缓解内存泄漏
为什么坚持用线性探测而不是链地址法
这不是妥协,而是精准匹配使用场景的设计选择:
- 每个线程的 ThreadLocal 数量通常很少(几十个以内),探测路径短,平均性能好
- 数组连续存储,CPU 缓存友好;没有 Node 对象和 next 指针,内存开销更低
- 弱引用 key + 探测路径上的 stale 清理,天然适配线性结构;换成二次哈希,stale entry 会散落各处,无法连带清理
- 哈希质量高:threadLocalHashCode 使用魔数 0x61c88647(斐波那契散列),冲突概率本身就很低
关键细节不能忽略
线性探测看似简单,但几个底层机制让它真正可靠:
- Entry 继承 WeakReference<ThreadLocal>,key 弱引用、value 强引用——这是内存泄漏风险的根源,也是 stale entry 产生的前提
- 没有负载因子,不自动扩容;table 扩容只在首次创建或显式 rehash 时发生,成本高但极少触发
- get() 过程也走同样探测路径,遇到 stale entry 同样清理,读操作也参与内存治理
- remove() 是主动兜底手段;依赖探测清理是“尽力而为”,主动 remove 才能确保及时释放


















