jdk 8 的 concurrenthashmap 通过 cas + synchronized 锁单个桶头节点,实现更细粒度并发控制,相比 jdk 7 的分段锁显著提升吞吐量、降低内存与 gc 开销,并优化扩容、树化及全表操作性能。

因为分段锁在 JDK 7 及之前版本中已暴露结构性瓶颈:锁粒度不够细、内存开销大、扩容与树化不兼容,而 JDK 8 改用 CAS + synchronized 锁单个桶头节点,真正实现“操作谁、锁谁”,并发吞吐显著提升。
锁粒度从段降到单个桶头节点
JDK 7 的 ConcurrentHashMap 使用 Segment 数组,默认 16 段,每个 Segment 是一个 ReentrantLock,锁住整个段内所有桶。哪怕两个线程只写入同一 Segment 下的不同桶,也会相互阻塞。
- JDK 8 彻底移除 Segment,底层是 Node[] 数组,每个桶(tab[i])独立管理
- 插入时先定位桶:若为空,直接 CAS 写入,全程无锁
- 若桶非空(已有链表或红黑树),仅对首节点(即 tab[i] 指向的 Node)加 synchronized
- 不同桶之间完全无竞争;只有哈希冲突到同一桶时,才进入同步块
内存与 GC 开销大幅降低
每个 Segment 不仅包含 HashEntry[],还自带完整的 AQS 队列、等待线程链表、count 和 modCount 字段,16 个 Segment 至少多占 ~2KB 堆内存,且 GC 扫描压力倍增。
- JDK 8 中不再有 Segment 实例,锁对象就是桶头 Node 本身,轻量级得多
- 所有共享字段(val、next、hash)均声明为 volatile,配合 CAS 保障可见性,无需额外锁状态维护
- CounterCell[] 替代多个 count 字段,size() 计算更灵活,支持无锁估算
更好适配现代 JVM 与硬件特性
JVM 在 JDK 6+ 对 synchronized 做了深度优化(偏向锁、轻量级锁、自旋),锁一个普通对象头的成本远低于维护 16 套 ReentrantLock。
- CAS 用于无竞争路径(初始化 table、更新 sizeCtl、插入首个节点等)
- synchronized 仅兜底高冲突场景,作用范围严格限制在单个桶的临界区
- 扩容由多线程协作迁移(helpTransfer),每个线程负责部分桶,旧表与新表可并行读写
- 链表转红黑树(阈值 ≥8 & table≥64)天然适配桶级锁——只锁树根,不影响其他桶
全表操作不再受固定段数拖累
JDK 7 的 size() 必须遍历全部 16 个 Segment,逐个加锁读取 count,时间复杂度固定为 O(16),且易因某段正在写入而反复重试。
- JDK 8 的 size() 基于 baseCount + CounterCell[] 的 CAS 累加,主路径无锁,响应更稳定
- clear()、forEach() 等操作也基于数组遍历,逻辑扁平,不依赖段间协调
- get() 全程无锁,即使扩容中也能通过 ForwardingNode 无缝跳转至新表
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











