ConcurrentHashMap JDK 1.8 采用 CAS + synchronized 协同实现线程安全,synchronized 仅锁单个桶的头节点(链表首节点或红黑树根节点),CAS 处理无竞争写入,读操作全程无锁依赖 volatile 可见性。

ConcurrentHashMap 在 JDK 1.8 中不再使用分段锁(Segment),而是通过 CAS + synchronized 协同实现线程安全,其中 synchronized 的作用对象非常具体:只锁单个桶(bin)的头节点(Node),而不是整个 Map、整个数组,甚至不是整条链表或整棵树。
锁的是“桶的头节点”,不是整个结构
ConcurrentHashMap 底层是 Node[] 数组,每个数组位置称为一个“桶”。当发生写操作(如 put、remove)需要修改某个桶时:
- 如果该桶为空,先用 CAS 尝试插入新节点;成功则完成,无需加锁
- 如果该桶已有元素(即头节点非 null),则对这个头节点对象执行
synchronized (first) - 锁住的是 当前桶的链表头节点或红黑树根节点,其他桶完全不受影响
- 锁的持有时间极短,仅覆盖哈希计算、节点查找、插入/删除等关键步骤
synchronized 和 CAS 是分工协作的关系
CAS 负责无竞争场景下的快速写入,synchronized 处理竞争后的串行化保障:
在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
- CAS 用于初始化桶、插入第一个节点、协助扩容、更新 count 控制变量等原子操作
- 一旦发现桶非空且存在并发写入,就进入 synchronized 块,在临界区内安全地遍历链表/红黑树、插入新节点、调整结构
- 例如
putVal()方法中:先 CAS 尝试插头节点;失败则 synchronized 锁头节点,再检查是否需转树、是否要扩容
为什么选 synchronized 而不是 ReentrantLock?
这是 JDK 1.8 的关键优化决策,基于实际性能与实现简洁性:
立即学习“Java免费学习笔记(深入)”;
- JDK 1.6+ 后 synchronized 已深度优化:支持偏向锁、轻量级锁(基于 CAS)、自旋、锁消除和锁粗化
- 在“锁持有时间极短 + 锁竞争不激烈”的典型场景下,synchronized 的开销已接近甚至优于 ReentrantLock
- 语法更简洁,JVM 层面可做更多逃逸分析和优化(比如锁消除),而 ReentrantLock 需显式 lock/unlock,易出错
- 细粒度锁本身已大幅降低竞争概率,没必要引入更重的可重入锁机制
读操作全程无锁,靠 volatile 保证可见性
synchronized 主要服务于写操作,而读操作设计为完全无锁:
- Node 数组、Node 的 next 和 val 字段均用
volatile修饰 - get() 方法直接按地址读取,不加锁也不 CAS,依赖 volatile 的 happens-before 语义确保看到最新值
- 这也意味着迭代器是弱一致性的:遍历时可能看不到某些刚写入的元素,也不会抛 ConcurrentModificationException

















