ConcurrentHashMap写入冲突时采用“自旋+CAS”机制确保最终成功:初始化阶段、插入空桶位、协助扩容期间均通过for(;;)循环配合CAS重试,CAS成功则退出,失败则重新读取状态再试,必要时yield让出CPU,并在高竞争下退化为synchronized兜底。

ConcurrentHashMap 在写入发生冲突时,不依赖阻塞等待,而是靠“自旋 + CAS”组合机制确保操作最终成功。核心不是避免冲突,而是让线程在冲突后主动重试,直到成功或条件变化。
自旋发生在哪些关键环节
自旋不是全局统一的,而是嵌套在具体操作路径中,主要出现在三个地方:
- 初始化阶段:首次 put 时 table 为 null,多个线程会同时进入 initTable() 的自旋循环,仅有一个能通过 CAS 设置 sizeCtl = -1 并完成初始化,其余线程检测到 sizeCtl == -1 后让出 CPU(如调用 Thread.yield()),然后继续检查 table 是否已就绪,未就绪则再次尝试。
- 插入空桶位:定位到某个桶(table[i])为 null 时,尝试用 CAS 直接写入新 Node;若此时其他线程抢先写入,当前 CAS 失败,就回到外层 for 循环重新计算位置、检查状态,再次尝试。
- 协助扩容期间:当检测到当前桶是 ForwardingNode(fwd),说明正在扩容,当前线程会主动参与迁移该桶或后续桶;迁移过程中对新表的操作也依赖 CAS,失败即重试,直到该桶迁移完成或自己分配到新任务。
CAS 是自旋得以生效的技术基础
自旋本身只是“反复执行”,真正决定是否重试、何时退出,靠的是 CAS 操作的原子返回值:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- CAS 成功(返回 true):表示内存值仍为预期旧值,本次更新有效,可退出自旋。
- CAS 失败(返回 false):说明期间被其他线程修改过,当前操作失效,必须重新读取最新状态再试。
- 例如:
U.compareAndSet(tab, i, null, new Node(h, key, value, null))—— 只有 tab[i] 确实还是 null,才会写入成功;否则立刻重来。
自旋不是无限制的“死等”
虽然源码中常见 for(;;) 形式,但实际有隐含退出条件和退避策略:
立即学习“Java免费学习笔记(深入)”;
- 每次循环都会重新读取 table 引用、长度、目标桶节点等关键变量,状态变化(如 table 初始化完成、桶被占、扩容启动)会自然引导流程转向新分支,而非卡在原地。
- 部分路径中(如 helpTransfer 方法)会在重试前调用 Thread.yield(),降低 CPU 占用;高竞争下也可能短暂让出时间片。
- 极端情况下,如果长期无法成功(如持续强竞争),JDK 不会无限自旋,而是借助 synchronized 锁定头节点进行兜底操作(如链表插入、树化),避免纯 CAS 导致的 CPU 空转问题。
为什么不用 synchronized 直接锁住整个操作
因为自旋+ CAS 实现的是细粒度、乐观、非阻塞的并发控制:
- 它只在真正发生写冲突的极小范围(比如单个桶)重试,不影响其他桶的读写,吞吐量远高于全局锁或分段锁。
- 读操作完全无锁,get 不参与自旋,也不受写线程影响,这是高性能的关键。
- 相比 synchronized 的“先自旋再挂起”,CAS 自旋更轻量,没有锁升级开销,适合短时、低冲突场景;而 ConcurrentHashMap 正是通过结构设计(如树化、扩容分担)把冲突概率压到足够低,使自旋高效可行。

















