ConcurrentHashMap 在 JDK 8 中弃用分段锁,改用 CAS + synchronized 组合机制,以降低锁粒度、减少竞争、提升吞吐量;底层采用 Node 数组+链表/红黑树结构,插入时仅对单个桶加锁,get 全程无锁,扩容支持多线程协作迁移。

ConcurrentHashMap 在 JDK 8 中彻底舍弃分段锁(Segment),转而采用 CAS + synchronized 的组合机制,核心目标是进一步降低锁粒度、减少线程竞争、提升高并发场景下的吞吐量。
为什么分段锁被舍弃
分段锁在 JDK 7 中虽比 Hashtable 全表锁更优,但仍存在明显瓶颈:
- Segment 数量固定(默认 16),无法随实际并发压力动态伸缩,低负载时浪费锁资源,高负载时仍可能多线程争抢同一 Segment
- 即使只操作一个桶(Bucket),也要锁定整个 Segment(含多个链表),锁范围过大
- size()、clear() 等全表操作需遍历全部 Segment 并加锁累加,时间复杂度为 O(16),且易因多次重试导致性能抖动
- 难以适配 JDK 8 引入的链表树化(红黑树)结构——Segment 锁无法自然覆盖树节点级别的细粒度操作
JDK 8 的新结构基础:Node 数组 + 链表/红黑树
底层回归与 HashMap 类似的单层数组结构(Node<K,V>[] table),每个数组槽位(bin)独立管理:
- 空槽位插入:直接用 CAS 原子写入新 Node,完全无锁
- 非空槽位插入:仅对该槽位的首节点(即链表头或树根)加
synchronized,锁范围精确到单个桶 - 链表转红黑树后,同步块仍作用于树根节点,保证树操作的原子性,不扩大锁边界
CAS 与 synchronized 各司其职
二者分工明确,避免“一刀切”加锁:
-
CAS 用于无竞争或低冲突场景:如初始化 table、设置扩容控制变量
sizeCtl、更新计数器 baseCount、插入首个节点等 -
synchronized 仅用于哈希冲突后的临界区:当发现目标 bin 已有节点(
f != null且未处于扩容中),才对f加锁,执行链表追加或树插入逻辑 - 所有共享字段(如
val、next)均声明为volatile,保障可见性,配合 CAS 实现安全读写
扩容机制也更轻量
不再依赖 Segment 级别扩容,而是数组级迁移:
- 扩容由多个线程协作完成:首个线程通过 CAS 设置
sizeCtl = -1标记开始扩容;后续线程检测到该标记,自动加入协助迁移 - 每个线程负责迁移特定区间桶,迁移完成后将对应位置设为
ForwardingNode(hash = -1),其他线程遇到即跳转至新表 - get 操作全程无锁,即使在扩容中,也能通过
ForwardingNode无缝读取新旧表数据

















