ConcurrentHashMap 通过分而治之与分级保障实现线程安全:JDK 8+ 采用 CAS + 桶级 synchronized,锁粒度降至单桶;读操作全程无锁,依赖 volatile 保障可见性;size() 使用 CounterCell[] 分段计数,扩容支持多线程协作。

ConcurrentHashMap 通过“分而治之 + 分级保障”的思路保证线程安全:不靠全局锁,而是根据操作类型和场景,动态选择最轻量、最精准的同步手段——该无锁时用 CAS,该加锁时只锁单个桶头,该读时完全不锁,全靠 volatile 语义支撑可见性。
JDK 8 及以后:CAS + 桶级 synchronized 是核心
这是当前主流 JDK 的实现方式,关键在于锁粒度降到最低:
- 桶为空时(
table[i] == null):直接用CAS尝试写入新节点,成功即完成,全程无锁 - 桶非空时:先获取桶头节点(
f = tabAt(table, i)),再对这个 具体对象(链表头或 TreeBin)加synchronized,其他桶不受影响 - 遇到扩容标记
ForwardingNode:不阻塞,而是协助迁移数据,再重试,把扩容也变成可并行任务
读操作全程无锁,靠 volatile 保证一致性
get 不加任何锁,但依然能读到相对可靠的结果,依赖三重 volatile 保障:
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
-
table数组声明为transient volatile,确保读线程能看到最新数组引用 - 每个
Node的val和next字段都是volatile,链表遍历时能逐跳看到最新值 - 写操作在锁内完成结构修改后,一定通过 volatile 写或 CAS 把头节点“发布”到
table[i],构成安全发布(safe publication)
计数与扩容:多线程协作而非串行等待
传统 size() 容易成为瓶颈,ConcurrentHashMap 用更聪明的方式应对:
立即学习“Java免费学习笔记(深入)”;
-
size()不直接累加,而是维护一个CounterCell[]数组,各线程在不同槽位做 CAS 计数,最后汇总,避免争抢同一变量 - 扩容不是单线程任务:首个触发扩容的线程初始化新表,后续线程发现
sizeCtl 且是扩容状态,会主动帮忙迁移部分桶,大幅提升扩容吞吐 -
sizeCtl是核心状态变量:负数表示扩容中(绝对值是参与迁移的线程数),0 表示未初始化,正数是下次扩容阈值
和老版本(JDK 7)的关键区别
JDK 7 用 Segment 分段锁,虽比 Hashtable 好,但仍有明显局限:
- Segment 是
ReentrantLock实例,每个 Segment 管理一个子哈希表,锁粒度仍是“一段”,不是“一个桶” - size() 需要尝试两次无锁统计,失败则按顺序锁住全部 Segment —— 扩容或高并发下容易卡住
- 结构更重:三层嵌套(segments → segment → table → HashEntry[]),内存与寻址开销更大
- JDK 8 彻底移除 Segment,结构扁平化为
Node[],更贴近 HashMap,也更容易优化

















