Skip to content
极高困难

一句话答案

JDK8 用 CAS + synchronized 锁单个 Node 实现并发安全,数组+链表+红黑树结构,并发度等于数组长度。

核心要点

JDK 7:Segment 分段锁

ConcurrentHashMap
├── Segment[0](继承 ReentrantLock)
│   └── HashEntry[] table(小 HashMap)
├── Segment[1](继承 ReentrantLock)
│   └── HashEntry[] table
...
└── Segment[15](默认 16 个 Segment)
  • 将整个 Map 分为 N 个 Segment(默认16),每个 Segment 独立加锁
  • 不同 Segment 的操作可以并发,同一 Segment 串行
  • 并发度 = Segment 数量 = 16

JDK 8:CAS + synchronized(桶级别细粒度锁)

底层结构与 HashMap 一致:Node[] table + 链表/红黑树
关键变化:
  - 锁粒度从 Segment 细化到单个桶(数组槽)
  - 初始化:CAS 保证 table 只被初始化一次
  - 空桶插入:CAS 无锁插入
  - 非空桶操作:synchronized 锁住桶的头节点

JDK 8 的改进:

维度JDK 7JDK 8
锁机制ReentrantLock(Segment级)synchronized(桶级)+ CAS
锁粒度一个 Segment(多个桶)单个桶(最细粒度)
并发度固定(Segment数量,默认16)理论上等于数组长度
数据结构数组 + 链表数组 + 链表 + 红黑树
size()先不加锁尝试,失败再全Segment加锁CounterCell(LongAdder思想)

JDK 8 的 CAS 操作:

java
// 空桶插入:无锁
if (casTabAt(tab, i, null, new Node<>(hash, key, value)))
    break;

// 非空桶插入:加锁(锁桶头节点)
synchronized (f) {  // f = table[i]
    // 在链表/红黑树中操作
}

为什么 JDK 8 用 synchronized 而不是 ReentrantLock JDK 6 后 synchronized 经过大量优化(偏向锁/轻量级锁/锁升级),在竞争不激烈时性能与 ReentrantLock 相当,且 JVM 能更好地优化 synchronized(如锁消除、锁粗化)。

追问与易错

追问方向:

  • JDK7 和 JDK8 的实现有什么区别?(Segment 分段锁 vs CAS+synchronized)
  • size() 是怎么统计的?(baseCount + CounterCell 数组,类似 LongAdder)
  • 能保证复合操作的原子性吗?(不能,putIfAbsent 等方法是原子的,但组合操作不是)

易错点:

  • ❌ "JDK8 的 ConcurrentHashMap 不用锁了"——synchronized 锁的是链表头节点
  • ❌ 认为 ConcurrentHashMap 的迭代器是强一致的——是弱一致性

💡 记忆锚点

JDK7把银行分16个柜台(Segment),每柜台一把锁;JDK8升级成每个窗口独立锁(synchronized锁桶头),空窗口CAS无锁插队,并发度从16飙升到数组长度。