跳转至

JDK 8 对 ConcurrentHashMap 的锁优化

一、JDK 7 的分段锁

Segment[]  ← 每个 Segment 是一把 ReentrantLock
  └── HashEntry[]
  • 默认 16 个 Segment。
  • 并发度最多 16(写操作)。
  • 每个 Segment 有自己的 HashEntry 数组。

二、JDK 8 的优化

1. 放弃 Segment

直接用 Node[] 数组,每个桶独立。

2. CAS + synchronized

  • 桶空:CAS 插入,无锁。
  • 桶非空:synchronized 锁桶的头节点。

3. 并发度提高

锁粒度从"段"变成"桶"。桶数 = table.length(16/32/64...),并发度 = 桶数。

4. 链表转红黑树

链表 ≥ 8 转红黑树,最坏查找 O(log n)。

5. 更高效的计数

CounterCell[] 分段计数,避免 size 的 CAS 竞争。

三、为什么 synchronized 比 ReentrantLock 好

JDK 6 后 synchronized 做了大量优化(偏向锁、轻量级锁),性能和 ReentrantLock 差不多。而且:

  • 锁对象是桶头节点,JVM 能优化。
  • 不需要 AQS 的额外开销。
  • 代码更简洁。

四、对比

JDK 7 JDK 8
Segment (ReentrantLock) CAS + synchronized
锁粒度 段级(16) 桶级
数据结构 链表 链表 + 红黑树
并发读 无锁 无锁(volatile)
计数 全局 CAS CounterCell 分段

五、为什么不直接用 Lock

  • synchronized 在 JDK 6 后足够快。
  • 桶锁竞争时间短,synchronized 足够。
  • JVM 对 synchronized 有逃逸分析、锁消除等优化。

面试加分

  • JDK 8 ConcurrentHashMap 读操作完全无锁。
  • 写操作锁桶头,不是锁整个表。
  • 扩容时多线程帮忙迁移(helpTransfer)。