首页 游戏 软件 资讯 排行榜 专题
首页
业界动态
ConcurrentHashMap 源码解析(JDK8)高并发哈希表的终极实现

ConcurrentHashMap 源码解析(JDK8)高并发哈希表的终极实现

热心网友
24
转载
2026-04-15

一、先一句话抓住核心(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的valnext均为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并发编程与数据结构结合的典范之作。

来源:https://www.51cto.com/article/840678.html
免责声明: 游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

相关攻略

SoftICE for WIN95中文命令解说(十三)
网络安全
SoftICE for WIN95中文命令解说(十三)

SoftICE for WIN95中文命令解说(十三) Copyright (c) 1999 http: coobe cs hn cninfo net ~tianwei 命令: TABS 作用: 显示或修改在显示源文件时TAB键的宽度 语法: TABS [tab-setting] 用法: tab-

热心网友
04.28
Linux 进程与线程深度解析:fork()、exec()、线程原理,一次彻底搞懂
业界动态
Linux 进程与线程深度解析:fork()、exec()、线程原理,一次彻底搞懂

一、进程是什么:不只是 "一个程序 " 教科书上那句“进程是程序的一次执行”,听起来总有点隔靴搔痒,不够透彻。 在内核的视角里,事情要具体得多。一个进程,本质上就是一个名为 task_struct 的结构体。你可以把它想象成一张记录了这个执行单元所有家当的“户口本”或“档案表”。里面都记了些什么呢? 进

热心网友
04.17
Solaris 系统查看系统中CPU的数目
系统平台
Solaris 系统查看系统中CPU的数目

要查看系统处理器信息,psrinfo(1M)命令是标准工具。不过,在多内核与多线程技术普及的今天,直接运行命令看到的可能并非物理CPU的实际数量。这时,加上-vp参数就非常关键了。 来看一个典型的例子: psrinfo -vp The physical processor has 32 virt

热心网友
04.15
ConcurrentHashMap 源码解析(JDK8)高并发哈希表的终极实现
业界动态
ConcurrentHashMap 源码解析(JDK8)高并发哈希表的终极实现

一、先一句话抓住核心(JDK7 vs JDK8) 要深入理解JDK8 ConcurrentHashMap的精妙设计,必须从其演进历程入手。 在JDK7中,其核心设计是“分段锁”(Segment Locking):将整个哈希表划分为多个独立的Segment,每个Segment独立加锁。这种设计虽然比H

热心网友
04.15
2026款耀世16 Pro游戏本预售:i7-14650HX配RTX5060售8699元
礼仪与书信
2026款耀世16 Pro游戏本预售:i7-14650HX配RTX5060售8699元

IT之家 2 月 26 日消息,机械革命现已官宣 2026 款耀世 16 Pro 游戏本,该机将于 2 月 26 日开启预售,3 月 1 日正式开售,首发价 8699 元。该机整体延续机械革命家族式

热心网友
02.27

最新APP

宝宝过生日
宝宝过生日
应用辅助 04-07
台球世界
台球世界
体育竞技 04-07
解绳子
解绳子
休闲益智 04-07
骑兵冲突
骑兵冲突
棋牌策略 04-07
三国真龙传
三国真龙传
角色扮演 04-07

热门推荐

华硕ROG枪神魔霸新锐2026游戏本预约开启
科技数码
华硕ROG枪神魔霸新锐2026游戏本预约开启

华硕ROG正式发布2026款枪神、魔霸及魔霸新锐系列游戏本并开启预约。枪神系列分为标准版与超竞版,均搭载酷睿Ultra9处理器,超竞版可选RTX5090显卡并配备光显矩阵屏。魔霸系列采用AMD锐龙处理器,高配可选锐龙99955HX3D与RTX5070Ti显卡。魔霸新锐系列主打性价比,配备RTX5060显卡,面向预算有限的玩家。

热心网友
05.15
锐龙5 9600X单通道内存电竞性能实测 依然轻松胜出
科技数码
锐龙5 9600X单通道内存电竞性能实测 依然轻松胜出

内存价格高企,单通道DDR5成为高性价比装机方案,但会降低游戏性能。测试显示,锐龙59600X凭借Zen5大核架构及对内存低延迟的优化,在搭配单条DDR56000内存时,游戏性能损失较小。相比之下,酷睿Ultra200SPLUS系列更依赖高带宽,单通道下性能下滑明显。在多款热门电竞网游实测中,锐龙59600X性能领先,且整机性价比优势显著。

热心网友
05.15
神牛ML40摄影灯内置锂电池版发布 售价568元起
科技数码
神牛ML40摄影灯内置锂电池版发布 售价568元起

神牛发布ML40系列摄影灯,包含ML40Bi和ML40R两款。ML40Bi售价568元,内置锂电池,支持边充边用及NFC快速连接,侧重便携智能。ML40R售价698元,具备更广色温调节范围,侧重专业色彩控制。两者均采用磁吸设计,兼容丰富附件,满足不同布光需求。

热心网友
05.15
华硕850W氮化镓电源白金重炮手849元入手
科技数码
华硕850W氮化镓电源白金重炮手849元入手

华硕TUFGaming系列推出新款850W白金重炮手氮化镓电源,到手价849元。该电源符合ATX3 1规范,长度150mm,采用全模组设计,配备12V-2×6接口支持600W峰值功率。其获得双白金效率认证与A-噪声认证,内部使用氮化镓元件与长寿电容,搭配135mm静音风扇,并提供8年质保,主打高效、安静与持久稳定。

热心网友
05.15
Falcon USD是什么币?USDF稳定币市值排名与投资价值解析
web3.0
Falcon USD是什么币?USDF稳定币市值排名与投资价值解析

FalconUSD(USDF)是一种与美元挂钩的稳定币,旨在为Web3生态系统提供可靠的交易媒介和价值储存工具。其运作依赖于储备资产支持和透明审计机制,在DeFi、跨境支付等场景有应用潜力。了解其技术原理、市场定位及潜在风险,有助于理性评估这一新兴数字资产的价值与前景。

热心网友
05.15