threadlocalmap用线性探测法解决哈希冲突,边探查边清理复用stale entry;初始索引i=key.threadlocalhashcode&(table.length-1),要求table长度为2的幂;遇到key==null不终止而记录staleslot继续查找;未命中时在首个staleslot插入并触发cleansomeslots;remove调用expungestaleentry实现链式压缩清理。

ThreadLocalMap 用线性探测法解决哈希冲突,不是靠“找空位就停”,而是边探查、边清理、边复用——核心在于它把冲突处理和内存管理绑在了一起。
线性探测怎么走:从起始索引开始,每次+1向后找
每个 ThreadLocal 实例有唯一 threadLocalHashCode(由 0x61c88647 步长递增生成),初始索引计算为:
i = key.threadLocalHashCode & (table.length - 1)
这个操作要求 table 长度恒为 2 的幂。如果 table[i] 已被占用且 key 不匹配,就调用 nextIndex(i, len),即 i+1(越界则回绕到 0),继续检查 table[i+1]、table[i+2]……
- 遇到 key.equals(目标):命中,更新 value
- 遇到 key == null(stale entry):记录位置,继续往后找
- 遇到 table[i] == null:未命中,可在此插入新 entry
为什么不用空槽当终止条件?因为要顺手清理过期项
标准线性探测一碰到 null 就停止,但 ThreadLocalMap 的 getEntry 在遇到 key == null 时不会停,而是记下第一个 staleSlot,继续往后找——因为目标 key 可能就落在 stale entry 后面的某个位置。
- 若最终走到 null 槽才停止,且之前发现过 staleSlot,则触发 cleanSomeSlots(staleSlot, i),启发式扫描后续少量槽位
- 该清理会调用 expungeStaleEntry(staleSlot),把从 staleSlot 开始向后所有非 null key 的 entry 重新哈希、前移压缩,缩短后续探测链
- 这种设计让查找和清理自然耦合,避免 stale entry 累积拖慢性能
插入时怎么复用 stale slot 而不是等空槽
set 方法在探测过程中,一旦遇到 key == null 的 entry,会暂存其下标为 staleSlot,继续找;若全程没找到匹配 key,则把新 entry 插入到第一个 staleSlot(复用),而不是等到最后的 null 槽。
- 插入后立即调用 cleanSomeSlots(staleSlot, i),清理探测路径上更多 stale entry
- 若清理后 size ≥ threshold(len × 2/3),触发 rehash():先全量清理,再扩容(长度翻倍),并对所有有效 entry 重新哈希插入
- rehash 不是简单复制,而是逐个重新计算 hash 并线性探测定位,确保扩容后分布更紧凑
删除不是简单置空,而是向后压缩并链式清理
remove() 不只是清当前 entry,而是调用 expungeStaleEntry(i),执行三步:
- 将 table[i].value 设为 null,table[i].key 置 null(切断强引用)
- 从 i+1 开始向后扫描,把所有“本该落在 i 或更前位置”的有效 entry 向前移动(压缩空洞)
- 移动过程中若又遇到 key == null 的 entry,继续触发清理,形成链式效果
这套机制不依赖外部调用,也不启动后台线程,所有清理都发生在 get/set/remove 的主路径上,轻量但有边界——只扫当前探测链,不全表遍历。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











