外观
一句话答案
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 7 | JDK 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飙升到数组长度。