当前位置: 首页 > news >正文

ConcurrentHashMap在扩容的过程中又有新的数据写入是怎么处理的

ConcurrentHashMap 在扩容过程中处理新数据写入的机制是其高并发性能的关键设计之一。它采用了一套精巧的策略,确保在扩容(rehashing)这个重量级操作进行时,读写操作依然可以高效、安全地并发执行。

核心机制可以概括为:多线程协同扩容 + 数据迁移惰性化 + 节点类型标识


1. 扩容触发与 sizeCtl 控制

  • ConcurrentHashMap 的元素数量超过阈值(容量 × 负载因子)时,会触发扩容。
  • 扩容过程由一个核心变量 sizeCtl 控制。它不仅控制初始化和扩容状态,还记录了参与扩容的线程数。

2. 核心机制:多线程协同扩容 (Helping During Resize)

这是 ConcurrentHashMap(JDK 1.8+)最核心的设计。扩容不是由单个线程完成的,而是由发起扩容的线程和后续的读写线程共同协作完成的。

具体流程:
  1. 初始化扩容任务

    • 当一个线程(比如线程A)发现需要扩容时,它会创建一个新的、容量为原容量两倍Node 数组(nextTable)。
    • 它将 sizeCtl 设置为一个负数,这个负数的绝对值表示参与扩容的线程数量阈值(通常与CPU核心数相关,但有上限)。
    • 这个 nextTable 就是扩容的目标数组。
  2. 划分迁移任务

    • 扩容的本质是将旧数组(table)中的每个 bin(桶,即链表或红黑树)里的 Node 迁移到新数组(nextTable)的对应位置。
    • 整个旧数组被划分为多个迁移任务段。每个任务段通常包含一个或多个连续的 bin
  3. 迁移过程与 ForwardingNode

    • 参与扩容的线程(包括发起者和后来帮忙的线程)会从任务段分配器那里领取一个任务段(例如,处理从索引 i 开始的一段 bin)。
    • 线程开始处理这个任务段:
      • 遍历该段内的每个 bin
      • bin 中的每个 Node,根据其 hash 值重新计算在新数组中的位置(因为容量翻倍,位置可能是 ii + oldCap)。
      • 将这些 Node 按照新的位置规则,分别插入到 nextTable 的两个新 bin 中(形成两个新的链表或树)。
    • 关键一步:当一个 bin 的所有 Node 都成功迁移到 nextTable 后,在旧数组 table 的这个 bin 位置上,放置一个特殊的节点 ForwardingNode
  4. ForwardingNode 的作用

    • ForwardingNode 是一种特殊的 Node 子类。
    • 它的 hash 值被设置为一个常量 MOVED(值为 -1)。
    • 它的 valnext 字段为 null
    • 它的存在就是告诉所有后续访问这个 bin 的线程:“这个桶的数据已经迁移到新数组了,你应该去新数组 nextTable 里找!”

3. 新数据写入的处理:动态路由

现在回答核心问题:当扩容正在进行,又有新的 put 操作(写入)发生时,ConcurrentHashMap 如何处理?

  1. 定位 bin

    • 新线程(线程B)执行 put(key, value)
    • 它首先根据 keyhash 值计算出在旧数组 table 中的索引 i
  2. 检查 ForwardingNode

    • 线程B尝试获取 table[i] 的节点。
    • 关键判断:如果它发现 table[i] 是一个 ForwardingNode(即 tab[i].hash == MOVED):
      • 这意味着 bin i 的数据已经或者正在被迁移到新数组。
      • 线程B不会在旧数组上执行插入。
      • 线程B会直接转向新数组 nextTable
      • 它根据 keyhash 值计算出在 nextTable 中的正确位置 j
      • 然后在 nextTable[j] 上执行正常的 put 操作(加锁、插入、可能树化等)。
      • 结果:新数据直接写入了新数组 nextTable,完美避开了旧数组的迁移过程。
  3. 未遇到 ForwardingNode

    • 如果线程B发现 table[i] 不是 ForwardingNode(比如是一个正常的 Nodenull),说明这个 bin 尚未开始迁移。
    • 线程B会在旧数组的 table[i] 上执行正常的 put 操作(加锁、插入等)。
    • 后续处理:当扩容线程后来处理到这个 bin i 时,它会将线程B刚刚插入的这个新 Node 也一并迁移到 nextTable 中。这保证了数据的完整性。
  4. 主动参与扩容

    • 在 JDK 1.8 的 ConcurrentHashMap 中,执行 put 操作的线程,在成功插入数据后,会检查整个扩容是否接近完成。
    • 如果发现还有未迁移的 bin,并且当前参与扩容的线程数未达到 sizeCtl 的阈值,这个 put 线程可能会主动领取一个迁移任务段,帮助进行数据迁移,然后再返回。这极大地加速了扩容过程。

总结:如何处理新数据写入

  1. 动态路由:通过 ForwardingNode 作为“路标”,新写入的数据会被自动引导到新的 nextTable 中,确保新数据直接进入新结构。
  2. 旧数据兜底:如果写入时目标 bin 尚未被标记为迁移,数据会先写入旧数组,等待后续的迁移线程将其搬走。
  3. 协同工作:执行写入操作的线程不仅是“消费者”,还可能成为“生产者”(帮助迁移),共同推进扩容任务。
  4. 无锁读取get 操作同样会检查 ForwardingNode,如果是,则去 nextTable 查找,保证了读操作在扩容期间也能正确返回结果,且通常不需要加锁。

最终结果ConcurrentHashMap 在扩容期间,依然能高效地处理新的读写请求,实现了“扩容对业务操作透明”的目标,这是其高并发性能的基石。

http://www.dtcms.com/a/360587.html

相关文章:

  • 《Bishop PRML》10.1 (3) 理解VAE reconstruction loss
  • Redis 中的 Bitmap 与 Bitfield 及 Java 操作实践
  • python如何下载svg图片
  • 【Proteus仿真】数码管控制系列仿真——单个数码管控制/多数码管控制
  • leetcode 260 只出现一次的数字III
  • 你的数据是如何被保护的?
  • Linux系统的进程管理
  • vue3+vite+ts 发布npm 组件包
  • 查看所有装在c盘软件的方法
  • [知识点记录]SQLite 数据库和MySQL 数据库有什么区别?
  • DuckDB 内嵌分析:ABP 的「本地 OL盘快照」
  • 福彩双色球第2025100期号码推荐
  • 福彩双色球第2025100期数据统计
  • 吴恩达机器学习作业十一:异常检测
  • Docker 容器(二)
  • 机器视觉学习-day15-图像轮廓特征查找
  • Wi-Fi技术——OSI模型
  • 深度学习量化双雄:PTQ 与 QAT 的技术剖析与实战
  • 开源协作白板 – 轻量级多用户实时协作白板系统 – 支持多用户绘图、文字编辑、图片处理
  • globals() 小技巧
  • C++ 模板全览:从“非特化”到“全特化 / 偏特化”的完整原理与区别
  • Prometheus之启用--web.enable-remote-write-receiver
  • 基于muduo库的图床云共享存储项目(三)
  • 前端常见安全问题 + 防御方法 + 面试回答
  • 「数据获取」《中国工会统计年鉴》(1991-2013)(获取方式看绑定的资源)
  • 【人工智能99问】Qwen3简介(33/99)
  • 浅析NVMe协议:DIF
  • 多线程使用场景一(es数据批量导入)
  • 林曦词典|老死不相往来
  • 洛谷p2392kkksc03考前临时抱佛脚 详解(回溯,深度搜索法)