JDK 8 对 ConcurrentHashMap 的锁优化¶
一、JDK 7 的分段锁¶
- 默认 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)。