news 2026/4/23 10:41:35

解析ConcurrentHashMap:扩容与计数机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
解析ConcurrentHashMap:扩容与计数机制

🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐➕关注👀是作者创作的最大动力🤞

💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝+关注👀欢迎留言讨论

🔥🔥🔥(源码获取 + 调试运行 + 问题答疑)🔥🔥🔥 有兴趣可以联系我

🔥🔥🔥 文末有往期免费源码,直接领取获取(无删减,无套路)

我们常常在当下感到时间慢,觉得未来遥远,但一旦回头看,时间已经悄然流逝。对于未来,尽管如此,也应该保持一种从容的态度,相信未来仍有许多可能性等待着我们。

🔥🔥🔥(免费,无删减,无套路):java swing管理系统源码 程序 代码 图形界面(11套)」
链接:https://pan.quark.cn/s/784a0d377810
提取码:见文章末尾

JDK8 ConcurrentHashMap:高并发下的扩容与计数机制深度解析

引言

在并发编程的世界中,ConcurrentHashMap 一直是 Java 开发者的重要武器。JDK8 对其进行了革命性的重构,抛弃了分段锁的设计,采用了更先进的 CAS + synchronized 机制。其中最值得深入研究的两个特性是:多线程协助扩容机制基于 CounterCell 的并发计数机制。本文将深入剖析这两大核心机制的设计原理和实现细节。

一、ConcurrentHashMap 的扩容机制

1.1 扩容触发条件

在 JDK8 的 ConcurrentHashMap 中,扩容主要发生在两种情况下:

  1. 元素数量超过阈值:当表中的元素数量超过容量 × 负载因子(默认 0.75)时

  2. 链表过长:当单个桶的链表长度超过 8,但表长度小于 64 时(此时优先扩容而不是树化)

1.2 ForwardingNode:扩容的"信号灯"

ForwardingNode是扩容机制中的关键节点,它是一个特殊的 Node 类型,哈希值为MOVED(-1)。当一个桶完成迁移后,会在这个位置放置一个 ForwardingNode。

static final class ForwardingNode<K,V> extends Node<K,V> { final Node<K,V>[] nextTable; ForwardingNode(Node<K,V>[] tab) { super(MOVED, null, null, null); this.nextTable = tab; } }

这个设计巧妙之处在于:

  • 对于读操作:ForwardingNode 知道数据已经迁移到新表,可以直接到新表中查找

  • 对于写操作:遇到 ForwardingNode 的线程会协助进行扩容

  • 对于迭代器:可以正确地在扩容期间遍历数据

🔥🔥🔥(免费,无删减,无套路): Python源代码+开发文档说明(23套)」
链接:https://pan.quark.cn/s/1d351abbd11c
提取码:见文章末尾

🔥🔥🔥(免费,无删减,无套路):计算机专业精选源码+论文(26套)」
链接:https://pan.quark.cn/s/8682a41d0097
提取码:见文章末尾
🔥🔥🔥(免费,无删减,无套路):Java web项目源码整合开发ssm(30套)
链接:https://pan.quark.cn/s/1c6e0826cbfd
提取码:见文章末尾

🔥🔥🔥(免费,无删减,无套路):「在线考试系统源码(含搭建教程)」

链接:https://pan.quark.cn/s/96c4f00fdb43
提取码:见文章末尾

1.3 多线程协助扩容机制

transfer() 方法的核心思想

扩容的核心在transfer()方法中实现,其设计哲学是"分工协作、最小冲突"

  1. 分而治之:将整个表分成多个"迁移段",每个线程负责一个段

  2. 逆序迁移:从后向前处理桶,避免并发处理时的冲突

  3. CAS 控制:通过 CAS 操作分配迁移任务,确保线程安全

private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) { int n = tab.length, stride; // 计算每个线程处理的桶数量,最小为16 if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) stride = MIN_TRANSFER_STRIDE; // 初始化新表,长度为原表的2倍 if (nextTab == null) { try { @SuppressWarnings("unchecked") Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; nextTab = nt; } catch (Throwable ex) { sizeCtl = Integer.MAX_VALUE; return; } nextTable = nextTab; transferIndex = n; // 从后向前迁移 } // 具体的迁移逻辑... }
协助扩容的触发条件

当线程在 put、remove 等操作时遇到 ForwardingNode,会触发协助扩容:

final V putVal(K key, V value, boolean onlyIfAbsent) { // ... 省略其他代码 else if ((fh = f.hash) == MOVED) // 遇到ForwardingNode tab = helpTransfer(tab, f); // 协助扩容 // ... 后续处理 }

1.4 扩容期间的读写操作

读操作如何并行

读操作在扩容期间完全无锁:

  1. 如果桶未迁移,直接读取

  2. 如果桶已迁移,通过 ForwardingNode 的 nextTable 转到新表读取

  3. 如果正在迁移,可能先读旧表再读新表

写操作如何处理
  1. 目标桶未迁移:正常进行 CAS 或 synchronized 操作

  2. 目标桶已迁移:通过 ForwardingNode 找到新表进行操作

  3. 正在迁移的桶:等待迁移完成后再操作,或者协助迁移

二、基于 CounterCell 的并发计数机制

2.1 传统计数方案的瓶颈

在高并发环境下,简单的原子变量(如AtomicLong)会导致严重的CAS 竞争。当数百个线程同时更新同一个计数器时,大量 CPU 时间浪费在 CAS 失败和重试上。

2.2 LongAdder 思想的引入

JDK8 的 ConcurrentHashMap 借鉴了LongAdder的思想,采用了"分段累加、最终汇总"的策略:

// 计数器的核心字段 private transient volatile long baseCount; private transient volatile CounterCell[] counterCells;

2.3 CounterCell 机制详解

结构设计
@sun.misc.Contended static final class CounterCell { volatile long value; CounterCell(long x) { value = x; } }

注意@sun.misc.Contended注解,这是为了防止伪共享(False Sharing)。CPU 缓存以缓存行为单位(通常 64 字节),如果没有这个注解,相邻的 CounterCell 可能会在同一个缓存行,导致一个线程更新时使其他线程的缓存失效。

更新流程

addCount()方法是计数的核心:

  1. 首选更新 baseCount:通过 CAS 尝试更新 baseCount

  2. 失败则使用 CounterCell:如果 CAS 失败,说明存在竞争,使用 CounterCell 数组

  3. 初始化或扩容 CounterCell:根据需要初始化或扩容 CounterCell 数组

  4. 在 CounterCell 上累加:通过哈希算法选择特定的 CounterCell 进行更新

private final void addCount(long x, int check) { CounterCell[] as; long b, s; // 第一步:尝试更新 baseCount if ((as = counterCells) != null || !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) { // 第二步:使用 CounterCell CounterCell a; long v; int m; boolean uncontended = true; if (as == null || (m = as.length - 1) < 0 || (a = as[ThreadLocalRandom.getProbe() & m]) == null || !(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) { // 第三步:初始化或扩容 CounterCell fullAddCount(x, uncontended); return; } // ... 省略后续处理 } // ... 省略后续处理 }

2.4 size() 方法的实现

size()方法并不是简单地返回一个值,而是需要汇总所有计数:

public int size() { long n = sumCount(); return ((n < 0L) ? 0 : (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int)n); } ​ final long sumCount() { CounterCell[] as = counterCells; CounterCell a; long sum = baseCount; // 基础值 // 累加所有 CounterCell 的值 if (as != null) { for (int i = 0; i < as.length; ++i) { if ((a = as[i]) != null) sum += a.value; } } return sum; }

注意:size() 方法返回的是近似值,因为在并发环境下,统计过程中可能有其他线程在更新。如果需要精确值,应该使用mappingCount()方法。

三、性能分析与设计思想

3.1 扩容机制的性能优势

  1. 避免单点瓶颈:传统 HashMap 扩容时整个表被锁定,而 ConcurrentHashMap 允许多线程并行迁移

  2. 减少扩容时间:N 个线程参与可以将扩容时间近似减少到 1/N

  3. 平滑扩容:读写操作在扩容期间基本不受影响

3.2 计数机制的性能优势

  1. 降低竞争:将热点分散到多个 CounterCell,减少了 CAS 冲突

  2. 伪共享防护:通过@Contended注解避免缓存行失效

  3. 惰性初始化:只有在确实存在竞争时才初始化 CounterCell 数组

3.3 实际应用建议

  1. 预估容量:如果知道大概的元素数量,创建时指定初始容量,避免频繁扩容

  2. 合理设置并发级别:虽然 JDK8 不再使用分段锁,但初始容量仍影响性能

  3. 理解 size() 的近似性:在高并发场景下,不要依赖 size() 的精确值做关键决策

  4. 监控 CounterCell 竞争:如果 CounterCell 数组过大,说明并发竞争激烈

结语

JDK8 的 ConcurrentHashMap 通过 ForwardingNode 实现的多线程协助扩容机制,以及基于 CounterCell 的分布式计数方案,展示了现代并发数据结构设计的精髓。这两大机制的共同特点是:

  • 避免全局锁:通过细粒度的控制和 CAS 操作

  • 化整为零:将大问题分解为小问题并行处理

  • 自适应调整:根据并发竞争程度动态调整策略

理解这些机制不仅有助于更好地使用 ConcurrentHashMap,更能启发我们在设计高并发系统时的思考方式。在面对并发问题时,"分而治之"和"减少共享"永远是最有效的两大法宝。

size() 方法执行流程

ConcurrentHashMap 扩容和计数机制:

ForwardingNode 在扩容中的作用:

ForwardingNode 转发机制

ConcurrentHashMap 的扩容流程和 CounterCell 计数机制的核心原理


往期免费源码对应视频:

免费获取--SpringBoot+Vue宠物商城网站系统

🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐➕关注👀是作者创作的最大动力🤞

💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝+关注👀欢迎留言讨论

🔥🔥🔥(源码 + 调试运行 + 问题答疑)

🔥🔥🔥 有兴趣可以联系我

💖学习知识需费心,
📕整理归纳更费神。
🎉源码免费人人喜,
🔥码农福利等你领!

💖常来我家多看看,
📕网址:扣棣编程
🎉感谢支持常陪伴,
🔥点赞关注别忘记!

💖山高路远坑又深,
📕大军纵横任驰奔,
🎉谁敢横刀立马行?
🔥唯有点赞+关注成!

⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇点击此处获取源码⬇⬇⬇⬇⬇⬇⬇⬇⬇

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/22 11:41:01

Syncovery Pro(自动备份同步工具)

链接&#xff1a;https://pan.quark.cn/s/ae601001b7bbSyncovery Pro是目前功能最为强大的实时自动备份工具&#xff0c;连FTP、WebDAV等全部支持&#xff01;最近从V6开始改用比较 好记、易懂的新名称 SynCovery 了。功能与SuperFlexibleSynchronizer仍然完全相同。基本简介 与…

作者头像 李华
网站建设 2026/4/16 17:15:22

精益生产到底是什么?七大浪费、五大原则、九大方法,一次讲清

很多人提到精益生产&#xff0c;心里都有个问号&#xff1a;“这个到底和我们业务有啥关系&#xff1f;”“看上去那么复杂&#xff0c;是不是又是个管理噱头&#xff1f;”其实&#xff0c;精益生产的核心是消除浪费&#xff0c;提高效率。它不仅仅是理论&#xff0c;而是一整…

作者头像 李华
网站建设 2026/4/19 0:40:42

深入理解 MySQL Buffer Pool 核心机制:初始化、free 链表与数据页流转

在MySQL的InnoDB存储引擎中&#xff0c;Buffer Pool&#xff08;缓冲池&#xff09;是提升数据库读写性能的核心组件——它通过将磁盘上的热点数据页缓存到内存中&#xff0c;避免了频繁的磁盘IO操作&#xff0c;让大部分数据访问都能在内存中完成。本文将从Buffer Pool的初始化…

作者头像 李华
网站建设 2026/4/22 2:28:01

springboot基于vue的的电影信息网站的设计与实现_51lpd2kg

目录已开发项目效果实现截图开发技术系统开发工具&#xff1a;核心代码参考示例1.建立用户稀疏矩阵&#xff0c;用于用户相似度计算【相似度矩阵】2.计算目标用户与其他用户的相似度系统测试总结源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&…

作者头像 李华
网站建设 2026/4/20 5:15:25

适合各行各业的开源万能表单源码系统 带完整的搭建部署教程

温馨提示&#xff1a;文末有资源获取方式这是一套经商业验证、功能完备的“交钥匙”工程&#xff0c;旨在帮助各行各业&#xff0c;以最小的技术投入&#xff0c;快速搭建起属于自己的信息交互与客户管理平台。源码获取方式在源码闪购网。一套系统&#xff0c;解决N个业务痛点&…

作者头像 李华
网站建设 2026/4/18 0:29:08

论文AI率90%→5%!DeepSeek四大降ai率指令+3款神器实测(保姆级教程)

从高校毕业论文到期刊审稿&#xff0c;2025年的AIGC检测已经成为了所有人的“噩梦”。很多同学拿着DeepSeek或GPT生成的论文去查&#xff0c;AI率直接飙红到90%。 别焦虑&#xff01;作为一个刚把AI率打下来的过来人&#xff0c;我发现&#xff1a;AI的痕迹其实是有迹可循的。…

作者头像 李华