ConcurrentHashMap扩容过程

news/2025/2/23 23:54:37/文章来源:https://www.cnblogs.com/jock766/p/18718398

一、ConcurrentHashMap扩容过程


1、ConcurrentHashMap扩容时新建数组


1.1 每个线程负责的数据迁移区域的长度:stride


1.2 关于transferIndex的说明


2、ConcurrentHashMap扩容时获取迁移数据区域


2.1 总结


3、判断数据迁移是否结束


3.1 每个线程完成自己区域内的数据迁移的判断条件


3.2 如何判断整个旧数组中的数据有没有迁移完


4、ConcurrentHashMap的数据迁移


4.1、ConcurrentHashMap数据迁移的原理


4.2、ConcurrentHashMap数据迁移的源码分析

  • 4.2.1 Node链表迁移的源码分析

    • 4.2.1.1 ConcurrentHashMap中如何确定节点在哪一条链表

    • 4.2.1.2 lastRun和链表数据的迁移流程

    • 4.2.1.3 链表迁移的思考

  • 4.2.2 红黑树迁移的源码分析


5、ConcurrentHashMap的数据迁移


5.1、ConcurrentHashMap扩容引起的数据丢失问题的原因及解决办法


二、多线程扩容问题

在put过程中,有2处地方会触发扩容的情况:

  • 在put完成之后,更新元素个数时发现元素个数已经超过扩容阈值sizeCtrl,这个时候就会触发扩容(addCount方法);

  • 在链表超过8,但是数组长度 小于 64时,不会将链表转换成红黑树,而是会选择扩容数组(tryPresize)


回到正文多线程环境下扩容与单线程环境的扩容有什么不同 ?

相同点:

  • 1、首先都需要新建一个数组用于扩容后的新容器;

  • 2、将现容器的数据迁移到新的容器中;

不相同点:

在单线程中所有的操作都是只有一个线程按顺序操作,而多线程则可能同时有多个线程操作同一件事;如果按照单线程的做法对扩容过程不加限制,会产生很多问题;比如在单线程中:创建新数组的操作,在多线程中旧可能出问题;如果多个线程同时扩容就可会创建多个数组;在迁移数据的同时又有新数据添加进来又该如何处理。。。因此在多线程环境下还必须要考虑到,数据迁移过程中可能出现对原数据的添加,删除,查询等问题。


回到正文多线程环境下扩容与单线程环境的扩容有什么不同 ?

  • 多线程环境下触发扩容条件之后,如何保证只有一个线程去新建新数组 ?

  • 在数据迁移过程中如果有数据的添加,删除,查询该怎么处理 ?

  • 在ConcurrentHashMap中是多个线程同时扩容的,那么如何协调多个线程同时扩容;

  • 如何确保数据全部迁移完成?


三、ConcurrentHashMap扩容过程

在ConcurrentHashMap中多个线程同时扩容时如何协同扩容呢 ?答案是:每个线程负责一部分固定的区域

扩容的源码比较多,因此先对扩容流程有一个整体的印象;再读源码;


1、ConcurrentHashMap扩容时新建数组


1.1、每个线程负责的数据迁移区域的长度:stride

在ConcurrentHashMap中每个线程的数据迁移区域长度stride 不是一个固定值,stride 的值是根据:数组长度和cpu的个数决定的;

stride计算分2步:

  • (NCPU > 1) ? (n >>> 3) / NCPU : n ;计算出stride的值

  • 与默认值比较;小于默认值使用默认值;大于默认值使用计算出的值;


1.2、关于transferIndex的说明

我们要确定一个线程的数据迁移区域,一个长度是不行的;还必须要知道一个数据迁移的起始的位置,这样才能通过:起始位置+长度;来确定迁移的范围;而transferIndex 就是确定线程迁移的起始位置;每个线程的起始位肯定都不同,因此这个变量会随着协助扩容的线程增加而不断的变化;


在ConcurrentHashMap中,分配区域是从数组的末端开始,从后往前配分区域,

  • 第一个线程起始迁移数据的下标:transferIndex -1(数组最大下标),分配区域:[transfer-1,transfer-stride],

  • 第二线程的起始位置:(transferIndex - stride-1),分配区域:[transferIndex - stride-1,transferIndex - 2 * stride],依次类推


2、ConcurrentHashMap扩容时获取迁移数据区域

当有新线程协助扩容时首先要获取到一个起始位置才能确定负责迁移数据的范围。ConcurrentHashMap是如何处理的?因为需要考虑到多个线程同时竞争同一个起始位置,因此要使用CAS竞争让线程抢位置,竞争失败的线程则进入循环下一次继续尝试获取一个起始位置;【在ConcurrentHashMap中大量的使用了CAS来修改变量值,如果关于CAS还有疑惑,可以看这篇文章中关于CAS的部分:关于CAS】

CAS 竞争位置,竞争成功之后由变量 i,bound 记录当前线程负责迁移数据的区域


2.1、总结

每个线程迁移数据时只能迁移获取到区域的数据:按顺序遍历的将获取到的区域: [i,bound]上每一个位置的数据完全迁移;但这里要注意下:i > bound ,迁移时顺序是 : i -> bound;


通过transferIndex 判断还有没有空闲的区域;transferIndex = 0 ;表示没有空闲区域了,所有区域都有线程负责迁移数据;


如果参与参与扩容的线程较多,那么可以将不同区域分配给不同线程;但如果参与扩容线程较少,那么线程完成了分配区域的数据迁移之后,获取下一块区域继续迁移数据,直到数据迁移完为止(为了保证在参与扩容线程很少时,也能将数据完全迁移);


A、有较多线程参与扩容时:


B、参与扩容线程较少:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/884762.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DVWA SQL注入-Low

1. 注入点判断 1 and 1=11 and 1=21 and 1=11 and 1=2,没有返回结果由此可判断注入点为数据类型,单引号闭合 2. 判断表列数 3 order by 3 # 遍历order by 参数,判断查询过的列数为2列3.确认显示位4.获取数据库名5. 获取数据库表名6. 获取数据库信息7. 获取数据表信息

安装,删除JDK

安装JDK百度搜索JDK8,找到下载地址 同意协议 下载电脑对应版本 双击安装JDK 记住安装的路径配置环境变量我的电脑>右键>属性>高级选项环境变量>JAVA_HOME>JKD路径配置path变量>%JAVA_HOME%\bin​ %JAVA_HOME%\jre\b…

人人都看得懂的DeepSeek入门科普

当大家都在好奇下一代AI会怎样改变我们的工作与生活时,中国公司 DeepSeek 正以惊人的速度和态度闯进大众视野。它究竟是什么,能做什么,又为何能在AI热潮里高调崭露头角?本文带你了解 DeepSeek 及其最新推出的两款大模型,顺便一起讨论它如何搅动整个 AI 行业的水面。 1. De…

LGP11261 [COTS 2018] 直方图 学习笔记

LGP11261 [COTS 2018] 直方图 学习笔记 Luogu Link 前言 参考了这篇题解。算是对其更详细的一个解释。 题意简述 给定一宽为 \(n\) 的直方图,第 \(i\) 格的高度为 \(h_i\)。也就是说,对于 \(\forall 1\le i\le n\),第 \(i\) 格矩形的四个顶点分别为 \((i-1,0),(i,0),(i-1,h_…

【FOFA】借助测绘探寻Ollma调用

免责声明: ⽂中所涉及的技术、思路和⼯具仅供以安全为⽬的的学习交流使⽤,任何⼈不得将其⽤于⾮法⽤途以及盈利等⽬的,否则后果⾃⾏承担。所有渗透都需获取授权!Fingers:app="Ollama" && is_domain=false根据Ollma官方接口可知 GET /api/tags HTTP/1.1 …

LCT(link cut tree)入门

简述 我们有这样一个问题:修改点权,询问链上的点权和。这明显是个树链剖分模版。 但如果还有这些操作呢:断开一条边,连上一条边,保证一直是森林。这就是动态树的一种问题。 而 LCT 就是解决这些问题的优秀数据结构。 前言 建议是会 Splay,虽然 FHQ-Treap 也能写,但是多一…

P1441 砝码称重(dfs)

要注意sum+a[i]的位置,我放错了,不知道为什么会re #include<iostream> #include<cstring> #define int long long using namespace std; int n,m,ans,a[30],vis[2020]; int f[2020]; int maxs; void check(){int sum=0;vis[0]=1;for(int i=1;i<=n;i++){if(f[i…

【PHP免杀】使用分支对抗进行Webshell Bypass

# webshell免杀 # PHP # 稻妻雷元素方块阵免责声明: 由于传播、利用本公众号所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,作者不为此承担任何责任,一旦造成后果请自行承担!前言 对于webshell免杀来说类绕过是最有效果且不易被检测出来的,…

springcloud-conifg升级后的擦坑《一》

configServer的配置文件: 之前老板的的路径为: - /springcloud-config/order/order-dev.yml [版本HxtonSR12] client1请求的: client2请求的: 根目录下的config配置文件 本文来自博客园,作者:余生请多指教ANT,转载请注明原文链接:https://www.cnblogs.com/wangbiaoh…

GDB调试(二)

GDB调试 运行中程序GDB调试 测试程序 //test2.c //功能:从0开始每秒打印 #include <stdio.h> #include <unistd.h> int aaa(); int bbb(int n); int main() {aaa(); }int aaa() {bbb(0); }int bbb(int n) {for(int i = n; i < n+10000; i++){printf("i:%d\…

qt cmake加入程序exe图标

可以看到qt自动编译出来的图标是默认的,如下图所示 我想要更改成自定义的图标,比如下方的样子 下边是操作步骤: 图标选择与转化成ico通过这个网站将正常图片转化成ico:https://www.bitbug.net/创建rc文件将ico复制到cmakelist的同级目录下,然后新建文本文件,里边输入如…

100道codeforces 2500

首先小小容斥一下,用1~r的减去1~l-1的。 1~r的,可以想到数位dp 设f[len][pre][mod]表示从低位数第len位,当前数字的值%2450为pre,当前用过的数字的lcm为mod的方案数 使用limit表示是否贴着上界 #include<bits/stdc++.h> using namespace std; typedef long long ll; …