在 Java 并发编程的世界里,ConcurrentHashMap 是高并发场景下线程安全 Map 的绝对首选 。它完美解决了 HashMap 线程不安全、Hashtable/Collections.synchronizedMap 全表锁性能极差的痛点。
而 ConcurrentHashMap 最核心的变革,就发生在 JDK 1.7 → JDK 1.8 的版本迭代中:从分段锁(Segment)彻底进化为桶级锁(数组节点锁)+ CAS 无锁编程,实现了并发性能、结构复杂度、查询效率的全面跃升。
本文就带你深度对比两个版本的设计思想,看懂这场经典的「并发锁优化进化史」。
一、核心定位:为什么要重构 ConcurrentHashMap?
先明确两个版本的共同目标:
- 保证线程安全,多线程下不出现死循环、数据覆盖、数据丢失;
- 尽可能提升并发效率,减少锁竞争、阻塞等待;
- 兼顾读写性能。
JDK 1.7 已经实现了基础的高并发,但存在明显缺陷:
- 结构复杂(二级哈希表);
- 锁粒度依然不够细;
- 查询链表效率低,且无法充分利用 CPU 多核并发。
因此 JDK 1.8 直接推翻重写,用更简单、更极致的设计,实现了性能翻倍。
二、JDK 1.7 ConcurrentHashMap:分段锁(Segment)设计
1. 核心数据结构:二级哈希表
JDK 1.7 采用 Segment 数组 + HashEntry 数组 + 链表 的结构:
ConcurrentHashMap
↳ Segment[] (分段数组,默认 16 个)
↳ HashEntry[] (每个 Segment 内部的哈希桶)
↳ 链表 (解决哈希冲突)
可以理解为:一个大 Map 里装了 16 个小 Hashtable。
2. 加锁机制:分段锁(Segment 锁)
- 每个 Segment 继承自
ReentrantLock; - 线程操作某个 key 时,只锁定对应的 Segment,不影响其他 Segment;
- 默认并发度为 16 → 最多支持 16 个线程同时并发操作。
3. 优点
- 相比全表锁的 Hashtable,并发能力大幅提升;
- 实现简单,满足中低并发场景足够用。
4. 致命缺点(JDK 1.8 必须重构的原因)
- 结构复杂:二级哈希表,内存占用高、查询路径长;
- 并发度受限:默认 16 个 Segment,最大并发度固定为 16;
- 锁粒度偏大:锁住的是整个 Segment(包含多个哈希桶),依然存在无效竞争;
- 查询效率低:纯链表存储,冲突多时 O (n) 遍历慢;
- CPU 利用不足:无法做到真正的细粒度无锁操作。
简单总结:分段锁是 "粗粒度锁 → 细粒度锁" 的过渡方案,不够极致。
三、JDK 1.8 ConcurrentHashMap:桶级锁 + CAS 无锁设计
JDK 1.8 直接抛弃 Segment,全面拥抱更简单、更高效的设计:
1. 核心数据结构:一级哈希表
Node[] 数组(哈希桶) + 链表 + 红黑树
和 JDK 1.8 HashMap 结构完全一致,结构极简、查询极快。
2. 加锁机制:桶级锁(数组头节点锁)+ CAS
这是最核心的进化:
- 锁粒度从 Segment → 单个哈希桶头节点;
- 读完全无锁,写优先 CAS 无锁,冲突才加轻量锁;
- 用
synchronized而非 ReentrantLock(JDK 1.8 后 synchronized 已大幅优化)。
3. 核心规则
- 读操作 :无锁,
volatile保证可见性; - 写操作 :
- 桶为空 → CAS 原子插入,无锁完成;
- 桶不为空 → 只锁住当前链表 / 红黑树的头节点;
- 哈希冲突优化:链表长度 ≥8 且数组≥64 → 树化为红黑树,O (logn) 高效查询;
- 并发扩容:多线程协同迁移桶,不阻塞全表。
4. 优势(碾压 1.7)
- 锁粒度极小:理论并发度 = 数组长度,支持数百线程同时操作;
- 无锁优先:大部分写操作可通过 CAS 无锁完成;
- 结构简单:一级数组,内存、查询效率更高;
- 红黑树加速:极端冲突下性能稳定;
- 并发扩容:不再是单线程阻塞扩容,效率大幅提升。
四、JDK 1.7 VS JDK 1.8 全面对比(核心总结)
| 维度 | JDK 1.7 ConcurrentHashMap | JDK 1.8 ConcurrentHashMap |
|---|---|---|
| 数据结构 | Segment + HashEntry + 链表 | Node 数组 + 链表 + 红黑树 |
| 锁实现 | ReentrantLock(分段锁) | synchronized + CAS(桶级锁) |
| 锁粒度 | Segment(多个桶) | 单个哈希桶头节点 |
| 并发度 | 默认 16(固定上限) | 数组长度(动态扩容提升) |
| 读操作 | 无锁(volatile) | 无锁(volatile,性能一致) |
| 写操作 | 锁定整个 Segment | CAS 无锁优先,冲突才锁节点 |
| 哈希冲突 | 链表(O (n)) | 链表 + 红黑树(O (logn)) |
| 扩容机制 | 单线程阻塞扩容 | 多线程协同并发扩容 |
| 结构复杂度 | 高(二级哈希) | 低(一级哈希,简单高效) |
| 高并发性能 | 良好 | 极佳(远超 1.7) |
一句话概括:JDK 1.7 是 "分段加锁",JDK 1.8 是 "无锁 + 极致细粒度锁"。
五、核心进化:为什么从分段锁 → 桶级锁?
这场重构的本质,是并发编程思想的升级:
1. 锁粒度越小,并发越高
- 全表锁(Hashtable)→ 分段锁(1.7)→ 桶级锁(1.8);
- 锁的范围越小,线程冲突概率越低,CPU 利用率越高。
2. CAS 无锁编程替代重量级锁
JDK 1.8 大量使用 CAS(Compare And Swap) 原子操作:
- 无锁、无阻塞、无上下文切换;
- 高并发下性能远超 ReentrantLock。
3. synchronized 优化完成
JDK 1.7 中 ReentrantLock 更高效;JDK 1.8 后 synchronized 拥有偏向锁→轻量级锁→重量级锁的自适应升级,配合桶级锁,性能反超 ReentrantLock。
4. 红黑树解决极端冲突
纯链表在高并发哈希冲突下会成为性能瓶颈,红黑树保证了最坏情况下的效率。