为什么ConcurrentHashMap的jdk8抛弃了ReentranceLock,选用了CAS+synchronized??
·
文章目录
这是一个非常精准的技术洞察。从 JDK 1.7 到 JDK 1.8,
ConcurrentHashMap 的改动被戏称为“王者归来”,因为它重新启用了曾经被认为性能不如 ReentrantLock 的 synchronized。
抛弃分段锁(Segment 继承自 ReentrantLock)转而采用 CAS + synchronized,主要是基于以下几个深层次的考量:
1. 锁粒度的极致优化
在 JDK 1.7 中,分段锁的粒度是 Segment。默认情况下,一个 Segment 包含多个桶。
- 1.7 版本:如果你想修改桶 A,而另一个线程在修改同段内的桶 B,你们依然会发生锁竞争。
- 1.8 版本:
synchronized只锁定当前桶的首节点。这意味着只要 Hash 不冲突,两个线程操作不同的桶完全不会相互阻塞,锁粒度从“段”细化到了“桶”级别。
2. JVM 对 synchronized 的深度优化
在早期的 Java 版本中,synchronized 是重量级锁,性能较差。但从 JDK 1.6 开始,JVM 对其进行了大量优化:
- 偏向锁、轻量级锁、自适应自旋:这些机制让
synchronized在竞争不激烈的情况下,开销几乎等同于无锁操作。 - 锁消除与锁粗化:JVM 可以在运行时根据代码执行情况,自动优化甚至消除不必要的锁。
- 内存开销:
ReentrantLock需要维护一个复杂的 AQS 队列同步状态,而synchronized是 JVM 维度的内置锁,每个对象头本身就携带状态位,不需要额外的对象实例来管理锁,更加节省空间。
3. CAS 与 synchronized 的精妙配合
JDK 1.8 并不是盲目使用 synchronized,而是采取了分级竞争策略:
- 第一步:CAS 尝试。如果桶是空的,直接通过
CAS操作(无锁)放入新节点。这避免了任何锁的开销。 - 第二步:synchronized 兜底。只有当发生 Hash 冲突(桶内已有数据)时,才会使用
synchronized锁定首节点进行链表或红黑树的遍历/插入。
4. 减少上下文切换(响应非公平锁原理)
正如你之前提到的非公平锁原理,synchronized 本身也是一种非公平锁。
- 当大量线程竞争同一个桶时,
synchronized的自适应自旋和非公平特性,能让新来的线程有概率直接获取锁,减少了线程频繁挂起和唤醒带来的内核态转换开销。 - 相比之下,如果用
ReentrantLock维护大量的细粒度锁对象,管理这些对象的开销在极端高并发下反而可能成为瓶颈。
核心对比总结
| 特性 | JDK 1.7 (ReentrantLock/Segment) | JDK 1.8 (CAS + synchronized) |
|---|---|---|
| 锁粒度 | 分段(Segment),默认 16 个。 | 哈希桶(Node),粒度极细。 |
| 内存占用 | 每一个 Segment 都是一个大对象。 | 无额外锁对象,直接利用 Node 首节点。 |
| 并发度 | 受限于 Segment 数量。 | 受限于数组大小,理论并发度更高。 |
| 技术选型 | 依赖 AQS 框架实现。 | 依赖 JVM 指令集优化 + CAS 原子操作。 |
简而言之,JDK 1.8 的设计思路是:能不加锁就不加锁(CAS),非要加锁就加最细的锁(synchronized 锁定桶首节点)。
更多推荐



所有评论(0)