ConcurrentHashMap核心源码(JDK1.8)
一、ConcurrentHashMap的前置知识扫盲
ConcurrentHashMap的存储结构?
数组 + 链表 + 红黑树
二、ConcurrentHashMap的DCL操作
HashMap线程不安全,在并发情况下,或者多个线程同时操作时,肯定要使用ConcurrentHashMap
无论是HashMap还是ConcurrentHashMap,在new好之后,数组并不是直接初始化好的。
如果是这种懒汉式的初始化方式,ConcurrentHashMap需要保证初始化数组时,是线程安全的。
看源码之前,先掌握一个ConcurrentHashMap的核心属性,这个属性是控制扩容和初始化数组的核心属性。
private transient volatile int sizeCtl;
sizeCtl是控制数组的初始化和扩容的。
sizeCtl == -1: 代表数组正在初始化。
sizeCtl < -1: 代表数组正在扩容
sizeCtl == 0: ConcurrentHashMap刚刚new好,并且没指定数组的初始化长度(默认长度为16)
sizeCtl > 0:
- ConcurrentHashMap刚刚new好,指定了数组的初始化长度,长度就是sizeCtl
- ConcurrentHashMap已经在使用了,sizeCtl代表扩容的阈值(数组长度 * 0.75)
了解sizeCtl之后,开始看初始化数组的源码。
// ConcurrentHashMap初始化数组的方法
private final Node<K,V>[] initTable() {// 声明了两个属性,tab,scNode<K,V>[] tab; int sc;// 判断数组初始化了咩? Checkwhile ((tab = table) == null || tab.length == 0) {// 没初始化,准备初始化操作!// 给sc赋值,并且判断数组是否正在初始化。if ((sc = sizeCtl) < 0)// 让出CPU的时间片,等待其他更多的机会完成初始化数组操作。Thread.yield(); // 没线程初始化,我来初始化。// 基于CAS的方式,将sizeCtl从原值改为-1,如果成功了,代表当前线程可以做初始化操作了。else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { // Lock// 当前线程要开始初始化数组了。try {// 再次判断数组初始化了咩? Checkif ((tab = table) == null || tab.length == 0) {// 数组没初始化。// 获取数组的初始化长度。如果sizeCtl > 0 ,就用sizeCtl作为初始化的长度,否则使用默认的16int n = (sc > 0) ? sc : DEFAULT_CAPACITY;// 创建数组!Node[] nt = new Node[n];// 将初始化好的数组,赋值给成员变量table = tab = nt;// 算出下次扩容的阈值sc = n - (n >>> 2);}} finally {// 将下次扩容的阈值赋值给sizeCtl,初始化完毕。sizeCtl = sc;}break;}}return tab;
}
三、ConcurrentHashMap的散列算法
ConcurrentHashMap基于key的一系列运算,最种得出元素要放到数组的哪个索引位置上。
暂时就认为ConcurrentHashMap是将key调用了hashCode得到了一个int类型的数值。
其实计算索引位置就是将数组长度 - 1和key的hashCode值做&运算得出的结果就是索引位置。
// 先优化一下代码,看的更清楚
((f = tabAt(tab, i = (n - 1) & hash)) == null)
// 代表拿到数组的某个索引位置的元素,f,如果为null准备插入数据
(f = table[(n - 1) & hash] == null)
// 这行就是在计算索引位置
(n - 1) & hash
n:数组的长度
hash:key.hashCode();
// n == 16
// hash:随便的一个int数值
00000000 00000000 00000000 00010000 :n 16
00000000 00000000 00000000 00001111 :n - 1 15
&
01010101 01010101 00110110 00101010 :hash
=
00000000 00000000 00000000 00001010 :index 10
如果ConcurrentHashMap的数组长度,允许17的话,会出现什么情况
00000000 00000000 00000000 00010001 :n 17
00000000 00000000 00000000 00010000 :n - 1 16
&
01010101 01010101 00110110 00111010 :hash
=
00000000 00000000 00000000 000 0000 :index 10
散列算法
目的就是让key的HashCode值的高低16位进行亦或运算,再和数组长度 - 1做&运算,最终得到元素存储的位置。
00000000 00000000 00000000 00010000 :n 16
00000000 00000000 00000000 00001111 :n - 1 1501010101 01010101 00110110 00101010 :name.hashCode();
00000001 01000001 00000110 00101010 :age.hashCode();散列算法就对这种情况做了优化!
散列算法:
(h ^ (h >>> 16)) & HASH_BITS;
优化一波
h ^ (h >>> 16)01010101 01010101 00110110 00101010 :name.hashCode()
^
00000000 00000000 01010101 01010101 :name.hashCode() >>> 16
=
00000000 00000000 00000000 00001111 : 最终name的hashCode00000001 01000001 00000110 00101010 :age.hashCode();
^
00000000 00000000 00000001 01000001 :age.hashCode() >>> 16
=
00000000 00000000 00000000 00001011 : 最终age的hashCode
为什么spread会让hash值和HASH_BITS做&运算
发现spread方法里,得到hash值之后,还做了一波&运算。
hash & HASH_BITS;
HASH_BITS = 01111111111111111111111111111111
hash值和HASH_BITS做&运算后,得到的结果除了最高位是0之外,其他位数没变化。
目的就是确保hash值算出来的一定是一个正数,因为负数有特殊含义。
static final int MOVED = -1; 如果存在数组中的数据的hash为-1,代表当前数组正在扩容!
static final int TREEBIN = -2; 如果存在数组中的数据的hash为-2,代表当前索引位置下挂的是红黑树!
static final int RESERVED = -3; 如果存在数组中的数据的hash为-3,当前当前数组的索引位置已经被占用了(value还没计算出来)
四、ConcurrentHashMap的并发安全(写数据)
首先确认ConcurrentHashMap在并发执行写操作时,线程是安全的。
同时还需要保证效率要高。
在JDK1.7中的实现是采用Segement分段锁的形式实现的。
Segement锁的本质就是ReentrantLock,一个Segement会管理多个索引位置,当操作指定索引位置前,需要先去或者这个索引位置对应的锁,再来执行操作。 这种方式在数组长度变长之后,效率也就一般般。
在JDK1.8中,采用的方式,可以实现为每一个索引位置都是一把独立的锁,不存在一个锁管理多个索引位置的情况,是一对一的方式。
代码实现的效果。 WCWCWCWCWCWCWCWCWCWCWCWCWC!!!!!
for (Node<K,V>[] tab = table;;) {// 省略部分代码 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {// 进到这,说明当前数组的索引位置,没数据,数据要放到数组上// 当数据要放到数组上时,基于CAS的形式存放。if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))break; }else {// 进到这,说明数组的索引位置有数据,数据要挂到链表或者红黑树上V oldVal = null;// f就是索引位置上的Node对象// 当操作时,根据数组上的f进行加锁,实现锁的细粒度化~synchronized (f) {// 省略部分代码 }}
}
五、ConcurrentHashMap的计数器和size方法
计数器:每次ConcurrentHashMap在写入一个数据后,需要+1,删除一个数据后,需要-1
size方法:帮你返回当前ConcurrentHashMap中的元素个数。
计数器需要保证线程安全的同时,实现++操作,一般就采用CAS,Java中在JUC好下,恰巧提供了Atmoic的原子类,内部已经帮你实现的了,基于CAS的++操作。
发现AtmoicInteger这种提供了increment操作的原子类中,是基于do-while + CAS实现的,如果并发比较大的话,会造成不停的CAS,导致浪费CPU资源。
所以ConcurrentHashMap并没有使用AtmoicInteger的方式去实现++的线程安全,是采用了一个LongAdder的实现机制。LongAdder有一个类似分段锁的概念。
ConcurrentHashMap并没有直接调用LongAdder,而是再次实现了LongAdder的核心代码。
size方法,就是将BaseCount和CounterCell数据的值进行一波统计,最终得出结果。
size中的核心就是sumCount方法,在内部就是拿到baseCount,然后遍历CounterCell[],将内部的每一个value做 +=,最终计算出元素个数。
final long sumCount() {CounterCell[] as = counterCells; CounterCell a;long sum = baseCount;if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)sum += a.value;}}return sum;
}
相关文章:

ConcurrentHashMap核心源码(JDK1.8)
一、ConcurrentHashMap的前置知识扫盲 ConcurrentHashMap的存储结构? 数组 链表 红黑树 二、ConcurrentHashMap的DCL操作 HashMap线程不安全,在并发情况下,或者多个线程同时操作时,肯定要使用ConcurrentHashMap 无论是HashM…...
【Python】文件 读取 写 os模块 shutil模块 pickle模块
目录 1.文件 1.1 读取操作 1.2 写操作 1.3 os:文件管理 1.4 os.path:获取文件属性 1.5 shutil:文件的拷贝删除移动解压缩 1.6 pickle:数据永久存储 1.文件 文件编码 编码是一种规则集合,记录内容和二进制间相互…...
PAT A1087 All Roads Lead to Rome
1087 All Roads Lead to Rome 分数 30 作者 CHEN, Yue 单位 浙江大学 Indeed there are many different tourist routes from our city to Rome. You are supposed to find your clients the route with the least cost while gaining the most happiness. Input Specific…...
浅谈HttpURLConnection所有方法详解
HttpURLConnection 类是 Java 中用于实现 HTTP 协议的基础类,它提供了一系列方法来建立与 HTTP 服务器的连接、发送请求并读取响应信息。下面是 HttpURLConnection 类中常用的方法以及其详细解释: ---------------------------------------------------…...
前端快速创建web3应用模版分享
一、起因 一直以来都有一个创建前端Dapp模版的愿望,一来是工作中也有这样的需要,避免每次都要抽离重复的代码。二来是这样的模版也能帮助其他前端快速了解到web3应用的脚手架以及框架结构。于是决定整理一个模版并开源,希望我的代码能帮助到大…...
越权漏洞讲解
越权漏洞是指系统或应用程序中存在的安全漏洞,允许攻击者以超越其授权范围的方式访问系统资源或执行特权操作。这种漏洞可能会导致严重的安全风险,因为攻击者可以利用它来获取敏感信息、修改系统设置或执行恶意操作。 下面是一些常见的越权漏洞类型和它…...

短视频矩阵源码系统打包.源码
Masayl是一款基于区块链技术的去中心化应用程序开发平台,可帮助开发者快速、便捷地创建去中心化应用程序。Masayl拥有丰富的API和SDK,为开发者们提供了支持。此外,Masayl还采用了高效的智能合约技术,确保应用程序的稳定、安全和高…...

云南LED、LCD显示屏系统建设,户外、室内广告大屏建设方案
LED大屏幕显示系统是LED高清晰数字显示技术、显示单元无缝拼接技术、多屏图像处理技术、信号切换技术、网络技术等科技手段的应用综合为一体,形成一个拥有高亮度、高清晰度、技术先进、功能强大、使用方便的大屏幕投影显示系统。通过大屏幕显示系统,可以…...

Shell脚本:expect脚本免交互
Shell脚本:expect脚本免交互 expect脚本免交互 一、免交互基本概述:1.交互与免交互的区别:2.格式:3.通过read实现免交互:4.通过cat实现查看和重定向:5.变量替换: 二、expect安装:1.…...

王道考研计算机网络第二章知识点汇总
2.1.1物理层基本概念 电气特性和功能特性易混淆,注意区分。电气特性一般指的是某个范围,功能特性一般指的是电平所代表的含义。 2.1.2数据通信基础知识 同步传输是指发送方和接收方节奏是统一的,数据之间是没有间隔的是一个一个的区块。在键…...

06.05
1.二进制求和 给你两个二进制字符串 a 和 b ,以二进制字符串的形式返回它们的和。 考虑一个最朴素的方法:先将 aaa 和 bbb 转化成十进制数,求和后再转化为二进制数。利用 Python 和 Java 自带的高精度运算,我们可以很简单地写出这…...

【虹科案例】虹科数字化仪在激光雷达大气研究中的应用
01 莱布尼茨研究所使用激光雷达进行大气研究 图 1:在 Khlungsborn 的 IAP 办公室测试各种激光器 大气研究使用脉冲激光束通过测量大气中 100 公里高度的多普勒频移和反向散射光来测量沿光束的温度和风速。返回的光信号非常微弱,会被阳光阻挡,…...

Java抽象类介绍
1 问题 声明一个名为Employee的抽象类,其中包含name(姓名)和sex(性别)两个String类型的私有属性,并声明一个继承于Employee抽象类的子类Teacher。 2 方法 2.1 定义一个抽象类:Employee。 2.2 为Employee类设计一个抽象方法。 2.3实现抽象类Em…...

适配器模式的运用
文章目录 一、适配器模式的运用1.1 介绍1.2 适配器模式结构1.3 类适配器模式1.3.1 类适配器模式类图1.3.2 代码 1.4 对象适配器模式1.4.1 对象适配器模式类图1.4.2 代码 1.5 应用场景1.6 JDK源码解析1.6.1 字节流到字符流的转换类图1.6.2 部分源码分析1.6.3 总结 一、适配器模式…...

2023/6/8总结
MySQL必知必会 commit 和 rollback 的差异是commit会提交,而rollback不会,就好像是撤回。 使用保留点: 简单的rollback和commit语句就可以写入或者撤销整个事务处理,但是,只是对简单的事务处理才能这样做࿰…...

AIGC大模型之——以文生图介绍
一、什么是以文生图? 以文生图是AIGC ( AI Generated Content )框架中的一个关键技术,通过文字描述,将文字转化为图像并展示出来。以文生图具有白动化程度高、精度高、可扩展性强、可定制化等优势,具有广泛的应用前景&…...

kali学习笔记(二)
一、关闭自动锁屏 关闭自动锁屏对于测试人员来说,可以按照自己的习惯来设置,不然kali会过十分钟就锁屏,有的时候会比较不方便。 1、使用root账号登录,在display设置选项中做如下设置。 2、把休眠选项关掉。 二、创建快照 关机创…...
avx指令集判断的坑
(一)背景 项目中依赖算法同学编写的算法模块,他们在使用avx,sse指令集来提高速度,结果在一些机器上崩溃,导致项目无法发版。 我给他们说,我们项目中使用了谷歌的 libyuv 库,也使用了…...
求内推,求明主!
个人资料: 性 别: 男 年 龄: 30岁 户 籍: 湖南衡阳 专 业: 计算机科学与技术 求职意向: Java软件开发工程师/JavaWeb开发工程师 现 居 地: 深圳市龙华新区 自考本科学历,6年工作经验(做过商城,APP,小程序,也研究多个开源案例,开源项目,并提交过PR) 自我评价: 做事积极主动,有责…...
第十三章:约束
第十三章:约束 13.1:约束(constraint)概述 为什么需要约束 数据完整性(Data Integrity)是指数据的精确性(Accuracy)和可靠性(Reliability)。它是防止数据库中存在不符合语义规定的数据和防止因错误信息的输入输出造成无效操作或错误信息而提出的。 为…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...

Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...

12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...

招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...