ConcurrentHashMap 是 Java 并发编程中高并发、线程安全 的哈希表实现,彻底解决了 HashMap 非线程安全、HashTable 全锁低效的问题。在 JDK1.8 版本中,它摒弃了 JDK1.7 的 ** 分段锁(Segment)** 设计,采用 volatile + CAS + synchronized 三重机制,实现了细粒度、高性能的线程安全,这也是其并发核心所在。
一、先明确:JDK1.8 ConcurrentHashMap 底层结构
在理解三重保障前,先掌握核心数据结构,这是机制生效的基础:数组 + 链表 + 红黑树
- 底层是Node 数组 (
transient volatile Node<K,V>[] table),作为哈希表的主体; - 哈希冲突时,转为链表存储;
- 链表长度超过 8 且数组容量≥64 时,链表转为红黑树,提升查询效率;
- 锁的粒度从分段锁缩小到「数组单个头节点」,只锁当前操作的节点,不影响其他数组位置,并发性能大幅提升。
二、三重保障核心机制详解
1. 第一重:volatile ------ 内存可见性 + 禁止指令重排
volatile 是 JMM(Java 内存模型)的轻量级同步机制,不保证原子性,但为 ConcurrentHashMap 提供基础的内存安全。
核心作用:
-
保证变量内存可见性 ConcurrentHashMap 核心变量(
Node数组table、节点的hash/key/next、sizeCtl)都用volatile修饰。- 一个线程修改了数组 / 节点的值,其他线程能立即读到最新值,不会读到过期的缓存数据;
- 避免多线程场景下,因内存不可见导致的数据不一致。
-
禁止指令重排 保证 Node 节点初始化、数组扩容时的执行顺序,防止线程读到「未初始化完成的半对象」。
关键源码体现:
// 核心哈希表数组,volatile保证可见性
transient volatile Node<K,V>[] table;
// 扩容时的新数组
private transient volatile Node<K,V>[] nextTable;
// 节点内部:hash、key、next全用volatile修饰
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
}
总结 :volatile 是基础防线,解决多线程间数据可见性问题,为 CAS 操作提供前提。
2. 第二重:CAS ------ 无锁高效操作,保证原子性
CAS(Compare And Swap,比较并交换)是CPU 原语级别的原子操作 ,属于无锁编程,是 ConcurrentHashMap 「读无锁、写少锁」的核心。
核心原理:
CAS 包含三个参数:内存值 (V)、预期值 (A)、新值 (B)
- 只有当内存值 V == 预期值 A 时,才将内存值修改为 B;
- 如果不相等,说明其他线程已修改,当前线程不阻塞、重试(自旋);
- 整个操作是原子性的,CPU 层面保证不可中断。
在 ConcurrentHashMap 中的核心场景:
-
数组头节点无锁插入 向数组某个位置添加元素时,如果该位置为空 (没有哈希冲突),直接用 CAS 尝试将新节点赋值到数组位置,全程不加锁,多个线程同时操作不同数组位置时完全并行。
-
sizeCtl 控制(扩容 / 初始化标记) 用 CAS 原子修改
sizeCtl变量,保证数组初始化、多线程扩容时,只有一个线程能执行关键操作,避免重复初始化 / 扩容。 -
自旋重试CAS 失败时不会阻塞线程,而是通过循环重试,减少线程上下文切换的开销。
关键源码体现(添加元素):
// 数组位置为空,直接CAS插入新节点,无锁
if ((p = tab[i = (n - 1) & hash]) == null) {
if (tab.casTabAt(i, null, new Node<K,V>(hash, key, value, null)))
break;
}
总结 :CAS 是高效防线,无锁实现原子操作,最大化并发性能,仅在冲突时才退化为加锁。
3. 第三重:synchronized ------ 细粒度锁,解决重度冲突
CAS 虽高效,但哈希冲突严重 时(链表 / 红黑树节点已存在),大量线程自旋会浪费 CPU。此时 JDK1.8 采用 synchronized 加锁,作为兜底保障。
核心设计:锁头节点,而非整个数组
这是 JDK1.8 最大的优化:
- 不再锁整个 Map、不再锁分段,只锁当前操作的数组位置的「头节点」;
- 不同数组位置的操作完全并行,互不干扰;
- 锁升级:JDK1.8 对
synchronized做了优化(偏向锁→轻量级锁→重量级锁),性能极高。
在 ConcurrentHashMap 中的核心场景:
-
链表 / 红黑树节点插入 / 修改 数组位置已存在节点(哈希冲突),
synchronized锁定头节点,保证同一时间只有一个线程操作该链表 / 红黑树。 -
节点扩容、红黑树转换涉及链表转红黑树、节点迁移时,加锁保证操作原子性,避免数据错乱。
-
覆盖已有元素修改已存在的 key 对应 value 时,加锁保证线程安全。
关键源码体现:
// 哈希冲突,锁定当前数组位置的头节点p
synchronized (p) {
// 执行链表/红黑树的插入、修改逻辑
if (tabAt(tab, i) == p) {
// 链表插入逻辑
// 红黑树插入逻辑
}
}
总结 :synchronized 是安全兜底防线,细粒度加锁解决重度并发冲突,保证极端场景下的数据绝对安全。
三、三重机制协同工作流程(以 put 元素为例)
把三重保障串联起来,就是 ConcurrentHashMap 线程安全的完整逻辑:
- volatile 保障 :线程读取
Node数组table时,一定是最新值,无内存不可见问题; - 计算索引:根据 key 哈希值,定位到数组的目标位置;
- CAS 无锁插入:如果目标位置为空,用 CAS 原子插入新节点,成功则结束;
- synchronized 细粒度加锁 :如果目标位置不为空(哈希冲突),锁定头节点;
- 安全操作:加锁后,向链表 / 红黑树插入 / 修改节点,保证线程安全;
- 解锁完成:操作完毕释放锁,其他线程可继续操作该节点。
四、三重保障核心对比
| 机制 | 作用 | 特性 | 核心价值 |
|---|---|---|---|
| volatile | 可见性 + 禁止指令重排 | 轻量级、无锁、非原子性 | 基础内存安全,为 CAS 做铺垫 |
| CAS | 原子操作、无锁 | 高效、自旋重试、无阻塞 | 高并发无锁操作,提升性能 |
| synchronized | 细粒度原子性、互斥 | 互斥、阻塞、锁升级 | 解决重度冲突,保证绝对安全 |
五、核心优势
- 高性能:读操作完全无锁,写操作仅锁头节点,并发能力远超 HashTable;
- 线程安全:三重机制覆盖「可见性、原子性、互斥性」,彻底解决并发安全问题;
- 自适应:无冲突用 CAS、有冲突用轻量级锁,冲突剧烈时锁升级,兼顾性能与安全。
总结
JDK1.8 ConcurrentHashMap 的线程安全,本质是分层防护:
volatile做基础可见性保障;CAS做高并发无锁操作;synchronized做细粒度锁兜底;三者协同,实现了「高并发、高性能、绝对安全」的哈希表,是 Java 并发编程的经典设计。