threadlocalmap采用线性探测法但不以空槽为查找终止条件,而是通过key==null或key.equals()判断;entry弱引用key,探测步长为1,查找/插入时主动清理stale entry并启发式重哈希。

ThreadLocalMap 使用线性探测法解决哈希冲突,但它的实现和标准教科书中的线性探测有关键差异:它不依赖“空槽”作为查找终止条件,而是靠 key == null(stale entry)或 key.equals() 成功来判断命中,且删除时采用“启发式清理”而非简单置空。
核心机制:基于弱引用 + 探测步长为 1 的开放寻址
ThreadLocalMap 的数组是 Entry[] table,其中 Entry 是继承自 WeakReference 的静态内部类,key 为 ThreadLocal 实例(弱引用),value 为用户存入的对象。
- 哈希值由
threadLocalHashCode & (len - 1)计算(即取模,要求容量恒为 2 的幂) - 发生冲突时,从起始下标开始,**每次 +1 向后探测**(table[i], table[i+1], table[i+2]…),直到遇到:
- key == null:说明是 stale entry(被 GC 回收的 key),继续探查(可能复用该位置)
- key.equals(目标 ThreadLocal):查找/更新成功
- table[i] == null:未命中,可在此插入新 entry
getEntry:查找逻辑不以 null 槽为终点
标准线性探测遇到 null 就停,但 ThreadLocalMap 的 getEntry 在遇到 key == null 时会记录该位置(staleSlot),并继续往后找——因为 stale entry 后面可能还存着目标 key。
- 若最终找到匹配的 key,直接返回 entry
- 若走到 null 槽才停止,且之前发现过 staleSlot,则触发
expungeStaleEntry(staleSlot)清理并尝试重哈希后续有效 entry - 这种设计让“清理 stale entry”与“查找”自然耦合,避免内存泄漏累积
set:插入时主动清理 + 探测中复用 stale slot
set 方法在探测过程中,一旦遇到 key == null 的 stale entry,会先标记为 staleSlot,继续找;若最终没找到匹配 key,则把新 entry 插入到第一个发现的 staleSlot(复用),而不是最后的 null 槽。
- 插入后,再调用
cleanSomeSlots(staleSlot, i)启发式扫描少量后续槽位,清理更多 stale entry - 若清理后仍超过阈值(size ≥ threshold = len * 2/3),触发
rehash():先全量清理,再扩容(len - 注意:rehash 不是简单复制,而是对所有非 null entry 重新计算 hash 并线性探测插入——这保证了扩容后分布更均匀
remove:懒清理 + 向后压缩
remove 并不只清当前 entry,而是调用 expungeStaleEntry(i):
- 将
table[i].value = null,table[i] = null - 然后从 i+1 开始继续扫描,把后续所有“本该落在 i 或之前位置”的有效 entry 向前移动(压缩空洞)
- 这个过程会再次触发对移动后位置的 stale entry 清理,形成链式清理效果











