jdk 8 改用 cas + synchronized 锁单个桶头节点,实现“操作谁、锁谁”,解决 jdk 7 segment 锁粒度粗、内存浪费、扩容树化不兼容等问题,并重构并发控制为“按物理位置加锁 + 无锁辅助”。

因为分段锁(Segment)在 JDK 7 及之前版本中已暴露严重瓶颈:锁粒度仍太粗、内存浪费大、扩容与树化不兼容,而 JDK 8 改用 CAS + synchronized 锁单个桶头节点,能真正实现“操作谁、锁谁”,并发吞吐显著提升。
Segment 锁的固定数量导致负载不均和资源浪费
默认 CONCURRENCY_LEVEL = 16,意味着无论实际并发线程是 2 个还是 200 个,Segment 数量都不可变。低并发时 15 个锁闲置;高并发下多个线程频繁争抢同一 Segment(哪怕只操作该 Segment 内不同桶),造成无谓阻塞。
-
Segment是ReentrantLock子类,每个实例含完整 AQS 队列、等待线程链表等开销,16 个 Segment 就是 16 套锁状态,GC 压力明显高于 JDK 8 的轻量级头节点锁 - 当数据分布倾斜(如哈希碰撞集中),某个
Segment成为热点,其他 Segment 却空闲,无法动态伸缩 —— 这不是并发优化,是人为制造瓶颈
全表操作(如 size())在 Segment 模型下性能抖动大
JDK 7 的 size() 必须遍历全部 Segment,对每个 Segment 加锁再读取 count 字段,最后累加。这导致:
- 时间复杂度固定为 O(16),无法随实际桶数或活跃段数收缩
- 若某
Segment正在扩容或写入,size()可能重试多次,响应延迟不可控 - 无法支持弱一致性语义 —— JDK 8 改用
baseCount+CounterCell[],允许size()在无锁路径下快速估算,误差可接受
红黑树树化与 CAS 原子性无法在 Segment 架构下自然落地
JDK 8 引入链表转红黑树(阈值 8),但 Segment 锁作用于整个段,一次加锁要覆盖从链表头到树根的所有节点,既锁得过宽,又难以原子更新树结构指针。
- 树化过程涉及多节点链接/断开,需要 CAS 保障中间状态不被并发干扰 ——
Segment锁无法提供这种细粒度原子性 - 扩容时,
Segment内部的HashEntry[]要整体迁移,而 JDK 8 的桶级迁移可逐个桶推进,支持“读写并行”(正在迁移的桶走特殊逻辑,其余桶照常服务) -
synchronized锁在 JDK 6+ 已深度优化(偏向锁、轻量级锁、自旋),锁单个桶头节点的开销远低于维护 16 个独立ReentrantLock
真正关键的一点常被忽略:JDK 8 的变更不是简单“换锁”,而是彻底重构了并发控制契约 —— 从“按逻辑分区加锁”转向“按物理位置加锁 + 无锁辅助”。这意味着你不能再假设某个操作(比如 putIfAbsent)会阻塞整段数据,它的影响范围精确到一个桶的头节点;同时也意味着,如果你在业务中误用了 ConcurrentHashMap 的“强一致性幻觉”(例如依赖 size() 精确值做限流),问题会在 JDK 8 下暴露得更早、更真实。










