ConcurrentHashMap通过分桶锁、volatile可见性与CAS协作实现高性能线程安全:读操作无锁靠volatile保障,写操作仅锁对应桶头节点,扩容与计数由多线程CAS协同完成。

ConcurrentHashMap 通过“减少锁范围 + 利用内存可见性 + 分阶段协作”三类手段协同保障线程安全,不是靠一把大锁,而是让读尽量无锁、写只锁必要最小单元。
读操作不加锁,靠 volatile 保证可见性
get、containsKey 等读方法全程不加锁。关键在于:
- table 数组本身是 volatile 的,对它的读写具有 happens-before 语义;
- Node 节点的 val 和 next 字段也是 volatile 的,确保链表遍历时能读到最新值和完整结构;
- 底层使用 U.getObjectVolatile()(即 volatile 读)定位桶,避免指令重排导致看到“半初始化”的节点;
- 若读到 ForwardingNode(hash == -1),说明正在扩容,会自动跳转到新表继续查找,保持逻辑一致性。
写操作分情况处理:空桶用 CAS,非空桶锁头节点
put、compute 等写操作根据桶状态选择不同策略:
- 桶为空(tab[i] == null):直接用 casTabAt() 尝试插入新 Node,成功即完成,无锁;
- 桶非空但未迁移:先检查是否为 ForwardingNode;若是,则协助扩容后重试;
- 桶头是普通 Node 或 TreeBin:对这个头节点对象加 synchronized,仅锁该桶,不影响其他桶并发写入;
- 树化后操作的是 TreeBin 对象,同样 synchronized(TreeBin),而非整棵树。
扩容与计数:多线程协作 + CAS 控制状态
扩容(transfer)和 size 统计这类全局操作也避免了串行化:
- 扩容由首个触发线程发起,但其他线程发现 sizeCtl
- size 计数不依赖全局锁,而是用一个 CounterCell[] 数组分段累加,最后汇总,配合 CAS 更新 baseCount;
- sizeCtl 是核心状态变量:为正表示下次扩容阈值,为负且高 16 位为标识(如 -1 表示初始化中,-N 表示有 N-1 个线程在扩容)。
对比老版本:从 Segment 分段锁到桶级细粒度控制
JDK 7 的 ConcurrentHashMap 用 Segment 数组(默认 16 段),每个 Segment 是 ReentrantLock + HashEntry 数组,锁粒度是“段”;JDK 8 彻底去掉 Segment,把锁压到单个桶的头节点上,同时引入红黑树优化长链表查询,CAS 覆盖初始化、扩容标记、计数等场景——整体更轻量、更贴近硬件原子指令特性。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











