这是一个非常精准的技术洞察。从 JDK 1.7 到 JDK 1.8, ConcurrentHashMap 的改动被戏称为“王者归来”,因为它重新启用了曾经被认为性能不如 ReentrantLocksynchronized

抛弃分段锁(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,而是采取了分级竞争策略

  1. 第一步:CAS 尝试。如果桶是空的,直接通过 CAS 操作(无锁)放入新节点。这避免了任何锁的开销。
  2. 第二步: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 锁定桶首节点)

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐