ConcurrentHashMap 源码解析(JDK8)高并发哈希表的终极实现
一、先一句话抓住核心(JDK7 vs JDK8)
要深入理解JDK8 ConcurrentHashMap的精妙设计,必须从其演进历程入手。
免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈
在JDK7中,其核心设计是“分段锁”(Segment Locking):将整个哈希表划分为多个独立的Segment,每个Segment独立加锁。这种设计虽然比Hashtable的全局锁粒度更细,提升了部分并发能力,但锁的粒度依然是Segment级别,并发度受限于Segment数量,在高并发场景下仍有瓶颈。
JDK8的设计理念发生了革命性转变:数据结构回归经典的数组+链表/红黑树组合;锁机制则升级为CAS无锁算法配合synchronized锁定单个哈希桶(桶的头节点)。锁的粒度被极致细化至单个桶。这一改变带来了并发性能的质的飞跃。
因此,核心总结是:JDK8 ConcurrentHashMap彻底摒弃了分段锁,采用更先进的桶级别锁与无锁CAS操作相结合的设计,是真正为现代高并发应用而生的数据结构。

二、JDK8 ConcurrentHashMap 核心结构
深入源码,其核心数据结构清晰而高效:
public class ConcurrentHashMap {
// 核心哈希表数组,volatile保证引用可见性
transient volatile Node[] table;
// 链表转换为红黑树的阈值
static final int TREEIFY_THRESHOLD = 8;
// 红黑树退化为链表的阈值
static final int UNTREEIFY_THRESHOLD = 6;
// 允许进行树化的最小表容量
static final int MIN_TREEIFY_CAPACITY = 64;
// 核心控制变量,控制初始化、扩容与计数
transient volatile int sizeCtl;
}
三、核心 Node 节点(JDK8 真实结构)
底层存储单元Node节点的定义,是支撑高并发读写的基础:
static class Node implements Map.Entry {
final int hash;
final K key;
volatile V val;
volatile Node next;
// ...
}
这里有三个至关重要的设计要点:
首先,table数组本身被volatile关键字修饰,这确保了数组引用本身的可见性,任何线程都能立即感知到数组的扩容或重建。
其次,Node节点中的val(值)和next(下一个节点引用)也被声明为volatile,这为完全无锁的读操作奠定了内存语义基础。
最后,sizeCtl这个变量是整个ConcurrentHashMap的“控制中枢”。它身兼多职:既是表初始化的控制标志,又是扩容的触发阈值和协调器。透彻理解sizeCtl,就掌握了ConcurrentHashMap并发逻辑的核心。
四、核心流程:put 方法源码(最精简准确版)
理论结合实践,下面这个精简的putVal流程,涵盖了所有关键并发场景:
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null)
throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node[] tab = table;;) {
Node f; int n, i, fh;
// 场景1:表未初始化 → 执行初始化
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// 场景2:目标桶为空 → 尝试CAS无锁插入
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<>(hash, key, value, null)))
break;
}
// 场景3:桶头节点为特殊标记(MOVED)→ 正在扩容,协助扩容
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
// 场景4:发生哈希冲突 → 锁住桶头节点进行处理
else {
V oldVal = null;
synchronized (f) { // 锁粒度仅为当前桶!
if (tabAt(tab, i) == f) {
if (fh >= 0) {
// 在链表中插入或更新
} else {
// 在红黑树中插入或更新
}
}
}
}
}
addCount(1L, binCount);
return oldVal;
}
掌握此流程,足以应对大部分面试提问。它主要执行以下几步:
第一步,参数校验。key和value均不允许为null,这是保证并发环境下语义清晰性的铁律。
第二步,计算哈希。通过spread方法对键的hashCode进行二次扰动,使哈希分布更均匀,减少冲突。
第三步,进入自旋主循环。处理四个核心分支:若数组未初始化,则进行初始化;若目标桶为空,则使用CAS操作无锁插入新节点,这是性能最优路径;若检测到桶头节点为特殊转发节点(MOVED),说明正处于扩容阶段,当前线程会协助进行数据迁移;若发生常规哈希冲突,则使用synchronized锁住该桶的头节点,然后在链表或红黑树中执行插入或更新操作。
第四步,后续处理。成功插入后,通过addCount增加元素计数,并检查是否触发扩容。若链表长度达到阈值(8)且表容量达到最小树化容量(64),则将链表转换为红黑树以优化查询性能。
五、为什么 JDK8 这么强?
分析完核心流程,其强大之处可归纳为以下四点:
锁粒度极致细化。 仅锁定发生冲突的单个哈希桶,其他桶的读写操作完全不受影响,并行度达到理论最高。
无冲突则无锁。 在大多数情况下(桶为空),直接使用CAS操作完成插入,性能开销与HashMap几乎无异。
多线程协同扩容。 这是JDK8设计的精髓。扩容时,数据迁移任务被拆分为多个小任务(桶区间),允许多个线程并发参与迁移工作,极大提升了扩容效率,避免了长时间阻塞。
自适应数据结构。 根据冲突程度,在链表和红黑树之间自动转换。在遭遇极端哈希冲突或攻击时,能将操作时间复杂度从O(n)降至O(log n),保障了性能下限。
六、必须搞懂的关键细节(面试高频)
掌握宏观设计后,以下细节是区分“了解”与“精通”的关键。
1. 为什么 key 和 value 不能为 null?
HashMap允许存储null键和null值,但ConcurrentHashMap明确禁止。根本原因在于并发语义的清晰性:在并发环境中,如果允许null值,当调用get(key)返回null时,无法区分是该key不存在,还是该key对应的value本身就是null。这种二义性会破坏线程安全的约定,因此采取最严格策略,禁止null。
2. get 方法真・完全无锁
这是ConcurrentHashMap读性能极高的根本。get操作全程无需任何锁。其安全性依赖于volatile的内存可见性保证:table引用、Node的val和next均为volatile。只要写操作遵循规则更新这些volatile变量,读线程就能安全地看到最新结果。tabAt方法保证了读取桶头节点的原子性。
3. sizeCtl 的 4 种含义(超级高频)
此变量是理解整个Map并发控制的总开关,其值代表四种不同状态:
等于 -1:表示哈希表正在初始化中。
小于 -1:表示正在扩容。其数值的低位记录着正在参与扩容的线程数量。
等于 0:表示创建时未指定初始容量,将使用默认容量。
大于 0:这是最常见状态,表示下一次触发扩容的阈值(当前容量 * 负载因子,默认为0.75)。
4. 为什么链表长度 ≥8 才树化?
这个阈值基于严格的概率统计(泊松分布)。在理想的哈希函数下,单个桶中链表长度达到8的概率极低(约为千万分之六)。如果达到此长度,极有可能遇到了哈希碰撞攻击,或是hashCode实现质量极差。此时将链表转为红黑树,是为了在极端情况下将操作复杂度从O(n)降为O(log n),保障性能。
5. 树化必须满足两个条件
链表长度≥8仅是必要条件之一。另一个条件是当前哈希表的容量必须达到MIN_TREEIFY_CAPACITY(默认64)。如果表容量还很小,优先选择扩容(resize)来分散节点,而不是立即树化。因为扩容能从根源上减少冲突,而树化会引入额外的内存和性能开销。
6. 扩容机制(JDK8 灵魂)
扩容是ConcurrentHashMap并发设计的集中体现。触发条件是元素数量达到阈值(sizeCtl)。新容量为旧容量的两倍。迁移过程采用“分而治之”策略:将旧数组划分为多个迁移区间(stride),每个线程负责迁移一个区间内的桶。迁移过程中,已处理桶的头节点会被替换为一个特殊的ForwardingNode(其hash值为MOVED),其他线程在读写时遇到此标记,会主动加入协助迁移。直至所有数据迁移完成,才会将引用指向新table。整个过程高效、协同,避免了单线程扩容的瓶颈。
7. size () 是弱一致性
size()方法返回的数值不是实时精确值。因为CHM并未为了获取一个绝对准确的计数而全局加锁,那样代价过高。它是通过累加各个分段计数单元(CounterCell)的值来估算的。因此,在高并发场景下,它提供的是一个弱一致性的视图。若业务仅需大致数量,size()完全适用;若强依赖精确计数,则需考虑其他方案。官方推荐使用mappingCount()方法,它返回long类型,更不易溢出。
七、JDK7 vs JDK8 一张表总结
(此处保留原文结构,内容已整合至第一部分“先一句话抓住核心”)
八、总结(可直接背)
总而言之,JDK8的ConcurrentHashMap通过一系列精妙绝伦的设计,实现了业界领先的高并发性能:它摒弃了分段锁,采用数组+链表/红黑树的基础结构;读操作完全无锁,写操作在无冲突时使用CAS,有冲突时仅锁住单个桶;扩容过程支持多线程协同工作,极大提升了效率。同时,通过禁止null键值保证了并发语义的清晰性,通过弱一致性的size()优先保障了性能。
因此,在高并发场景下的选择非常明确:首选ConcurrentHashMap。而传统的HashMap(非线程安全)或Hashtable(性能低下)已不在考虑之列。其设计思想,堪称Java并发编程与数据结构结合的典范之作。
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
特斯拉 FSD v14.3 推送:反应速度提升 20%、全球车队协同学习
特斯拉FSD v14 3重磅推送:20%的速度革命与“群体智慧”觉醒 就在今天,特斯拉在美国正式向早期公开测试用户推送了FSD(监督版)v14 3。这次更新,可以说是被首席执行官埃隆·马斯克寄予厚望的里程碑——他曾将其形容为自动驾驶拼图的“最后一块重要部分”。在经历短暂内部测试并获得员工验证后,这项
村田量产 7 款大容量车载 MLCC,为智驾、电源线路应用提供高密度选择
村田启动七款大容量车载MLCC量产,为智能驾驶与电源管理注入高密度能量 汽车电子领域又迎来了关键性元器件的新进展。就在近日,村田制作所正式宣布,面向汽车应用的多层陶瓷电容器产品线迎来重量级更新——七款新品已启动量产。这批新品的特点非常明确:在特定的额定电压和封装尺寸下,实现了行业领先的“特大”静电容
索尼Xperia 1 VIII手机渲染图曝光:背部改用矩形相机岛设计
索尼 Xperia 1 VIII 渲染图曝光:标志性垂直三摄布局被取代 近日,关于索尼下一代旗舰手机的消息逐渐浮出水面。知名科技媒体 PhoneArena 在4月8日分享了一组据称是 Xperia 1 VIII 的渲染图,其中最引人注目的变化,莫过于机身背面设计的重大革新。 上图为现款索尼 Xper
特斯拉FSD V14.3版本推送:核心底层重构 反应速度提升20%
特斯拉FSD V14 3版本推送:核心底层重构 反应速度提升20% 特斯拉的版本更新,又一次把“主菜”藏在了深层技术里。4月8日,开始向搭载HW4硬件的车型推送的FSD V14 3系统,编号2026 2 9 6。这次的更新清单看起来颇为常规,但最重磅的改动,其实源自一个底层技术的彻底革新。 简单来说
iPhone 17系列2月全球销量增26%,中国成增长引擎与均价提升主因
iPhone全球销量强势增长,中国市场成核心引擎 最近,伯恩斯坦分析师在4月7日发布了一份引人关注的研究报告,其中揭示了一个关键数据:2026年2月,苹果iPhone的全球销量同比激增了26%。这个增幅,可比上一代iPhone 16同期的表现高出了整整20个百分点。你猜增长的动力主要来自哪里?答案指
- 日榜
- 周榜
- 月榜
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
相关攻略
2015-03-10 11:25
2015-03-10 11:05
2021-08-04 13:30
2015-03-10 11:22
2015-03-10 12:39
2022-05-16 18:57
2025-05-23 13:43
2025-05-23 14:01
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程
热门话题

