concurrenthashmap 迭代器能安全遍历扩容过程,是因为遇到 forwardingnode 时主动切换至新表并重新定位,而非跳过;forwardingnode 是 hash 为 -1、指向新表的占位节点,起“路标”作用,迭代器通过 traverser 检测并无缝跳转,结合分段懒迁移与双表协同机制,确保不漏读、不重复、不抛异常。

ConcurrentHashMap 在扩容过程中,迭代器能安全遍历是因为它对 ForwardingNode 做了特殊处理——不是跳过,而是主动“跟进”迁移目标,从而保证不漏读、不重复、不抛异常。
ForwardingNode 是什么
ForwardingNode 是扩容时占位用的特殊节点,其 nextTable 字段指向新表,hash 固定为 -1。它本身不存键值,只起“路标”作用。
- 当 get/put 遇到它,会转向新表对应位置继续操作
- 迭代器遇到它,也会顺着它的
nextTable去读新表,而不是停在原地或忽略
迭代器如何识别并处理 ForwardingNode
ConcurrentHashMap 的迭代器(如 KeyIterator、EntryIterator)底层使用 Traverser 类遍历。它在每次 advance 时检查当前节点:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 若节点是
ForwardingNode,就切换到新表(f.nextTable),重新计算该 bin 在新表中的起始位置 - 然后从新表对应位置继续扫描,相当于“无缝跳转”
- 这个过程对用户透明,迭代器仍按逻辑顺序推进,不会因扩容中断或乱序
为什么不会漏读或重复读
关键在于扩容是分段、懒迁移的,且迭代器有“双表协同”机制:
- 旧表未迁移的 bin 正常读取
- 已变成
ForwardingNode的 bin,迭代器立刻切到新表对应位置,并从那里开始读——而新表该位置的数据要么为空,要么是已完成迁移的节点链 - 由于迁移是单向、不可逆的(旧表节点只会被置为
ForwardingNode,不会回写),迭代器不会回到旧表重读同一数据
实际代码中的一次跳转示意
简化逻辑如下(源自 Traverser.advance()):
if (f instanceof ForwardingNode) {
tab = ((ForwardingNode)f).nextTable; // 切换到新表
// 重新计算当前步进在新表中的起始索引
i = (i
<p>这不是简单跳过,而是主动重定位,确保遍历流持续向前。</p>Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










