ConcurrentHashMap的CAS机制用于空桶插入、数组初始化和计数器更新三类轻量写操作;失败后降级为synchronized锁头节点,配合volatile保证可见性,形成无锁优先、有锁兜底的分层并发控制。

ConcurrentHashMap 的 CAS 机制不是单独起作用的,而是和 volatile 可见性、细粒度 synchronized 锁 配合,形成分层保障。它主要在“无竞争”或“轻量写入”场景下避免加锁,从而提升并发吞吐量。
CAS 在哪些关键操作中被使用
CAS(Compare-And-Swap)是 CPU 级原子指令,在 ConcurrentHashMap 中被用于以下三类高频率、低复杂度的写操作:
- 空桶插入:当 put 一个新 key,定位到的数组位置(即桶)为 null 时,直接用 CAS 尝试将新 Node 写入该位置。成功则完成插入;失败说明已有其他线程抢先写入,此时退回到加锁流程。
- 数组初始化:首次 put 时若 table 为 null,多个线程可能同时尝试初始化。通过 CAS 更新 table 字段,仅有一个线程能成功,其余线程检测到非 null 后直接参与后续操作,避免重复初始化。
- 计数器更新(baseCount 和 CounterCell):size() 方法不锁全表,而是维护一个 baseCount 基础计数 + 多个 CounterCell 分段计数单元。对 CounterCell 的更新大量依赖 CAS —— 每个线程尝试更新自己的 cell,冲突时换一个 cell 重试,最终汇总得到近似 size。
CAS 能安全运行的前提条件
CAS 不是万能的,它依赖三个底层支撑才能真正保证并发安全:
-
volatile 修饰关键字段:Node 的
val和next都是 volatile,确保一个线程通过 CAS 写入后,其他线程能立即看到最新值,不会因 CPU 缓存不一致读到旧数据。 - 内存地址固定且可预测:CAS 操作必须针对明确的内存偏移量(如 table 数组中第 i 个元素)。ConcurrentHashMap 使用 Unsafe 类直接操作内存地址,配合数组索引计算,保证每次 CAS 作用于正确位置。
- 失败后有确定的回退路径:CAS 失败不意味着操作终止,而是触发重试或降级。例如空桶插入失败后,会转为对头节点加 synchronized 锁,再走链表/红黑树插入逻辑 —— 这种“无锁优先、有锁兜底”的策略兼顾性能与可靠性。
为什么不用 CAS 处理所有操作
因为 CAS 只适合单变量、无状态依赖的原子更新。而以下操作无法靠 CAS 完成:
立即学习“Java免费学习笔记(深入)”;
- 向已有链表尾部追加节点(需先遍历、再修改 next,中间状态不可拆分)
- 红黑树的插入与平衡(涉及多节点指针调整、颜色翻转等复合操作)
- 扩容迁移(需同时操作新旧两个数组,协调多个线程进度)
这些场景必须由 synchronized 锁住头节点或特定锁对象,确保临界区串行执行。
CAS 和 synchronized 不是替代关系,而是协作关系
整个 put 流程就像一道安检闸机:
- 第一道门(CAS):快速放行无冲突的简单请求;
- 第二道门(synchronized):对需要深度操作的请求进行严格检查和隔离;
- volatile 是整条通道的透明玻璃墙:所有人随时能看到最新状态。
这种设计让读操作几乎完全无锁,90% 以上的写操作在低竞争时走 CAS 路径,只有真正发生哈希冲突或扩容时才进入重量级同步块 —— 正是它比 Hashtable 和 synchronizedMap 高效的根本原因。


















