【Java并发】聊聊concurrentHashMap的put核心流程

结构介绍

1.8中concurrentHashMap采用数组+链表+红黑树的方式存储,并且采用CAS+SYN的方式。在1.7中主要采用的是数组+链表,segment分段锁+reentrantlock。本篇主要在1.8基础上介绍下.
在这里插入图片描述
那么,我们的主要重点是分析什么呢,其实主要就是put 的整体流程。数组如何初始化,添加到数组、链表、红黑树、以及对应的扩容机制。

    //添加数据public V put(K key, V value) {return putVal(key, value, false);}

散列函数

put方法实际上调用的是putVal()方法。

 final V putVal(K key, V value, boolean onlyIfAbsent) {// 1.判断为空 直接返回空指针if (key == null || value == null) throw new NullPointerException();// key和value不允许nullint hash = spread(key.hashCode());//两次hash,减少hash冲突,可以均匀分布}

这里为什么要对 h >>> 16 ,然后在进行 异或运算 & 操作。

其实主要还是为了让hash高16位也参与到索引位置的计算中。减少hash冲突。

    static final int spread(int h) {return (h ^ (h >>> 16)) & HASH_BITS;}

我们假设h 位 :00011000 00000110 00111000 00001100

00011000 00000110 00111000 00001100  h
^
00000000 00000000 00011000 00000110  h >>> 1600011000 00000110 00111000 00001100 
&
00000000 00000000 00000111 11111111  2048 - 1ConcurrentHashMap是如何根据hash值,计算存储的位置?
(数组长度 - 1) &  (h ^ (h >>> 16))00011000 00000110 00110000 00001100  key1-hash
00011000 00000110 00111000 00001100  key2-hash
&
00000000 00000000 00000111 11111111  2048 - 1

Node中的hash值除了可能是数据的hash值,也可能是负数。

// static final int MOVED     = -1; // 代表当前位置数据在扩容,并且数据已经迁移到了新数组
// static final int TREEBIN   = -2; // 代表当前索引位置下,是一个红黑树。   转红黑树,TreeBin有参构造
// static final int RESERVED  = -3; // 代表当前索引位置已经被占了,但是值还没放进去呢。  compute方法

初始化数组

 final V putVal(K key, V value, boolean onlyIfAbsent) {if (key == null || value == null) throw new NullPointerException();// key和value不允许nullint hash = spread(key.hashCode());//两次hash,减少hash冲突,可以均匀分布int binCount = 0;//i处结点标志,0: 未加入新结点, 2: TreeBin或链表结点数, 其它:链表结点数。主要用于每次加入结点后查看是否要由链表转为红黑树for (Node<K, V>[] tab = table; ; ) {//CAS经典写法,不成功无限重试Node<K, V> f;int n, i, fh;//检查是否初始化了,如果没有,则初始化if (tab == null || (n = tab.length) == 0)tab = initTable();}

调用构造方法的时候,其实并没有进行初始化数组。而是延迟初始化操作。通过CAS的方式,先判断table数组为空的话,进行初始化。

   /*** 这里需要先介绍一下一个属性,sizeCtl是标识数组初始化和扩容的标识信息。= -1 正在初始化  < -1 : 正在扩容 =0 : 代表没有初始   > 0:①当前数组没有初始化,这个值,就代表初始化的长度!  ②如果已经初始化了,就代表下次扩容的阈值!*/private transient volatile int sizeCtl;//控制标识符
// 初始化操作数组private final Node<K, V>[] initTable() {Node<K, V>[] tab;int sc;// 先判断是否为空while ((tab = table) == null || tab.length == 0) {// 说明线程正在初始化或者正在扩容 线程让步if ((sc = sizeCtl) < 0)Thread.yield(); // lost initialization race; just spin// cas的方式将sizeCtl 修改成-1 正在初始化状态else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {try {// 再次判断数组是否初始化没有if ((tab = table) == null || tab.length == 0) {// 获取到数组初始化的长度 16int n = (sc > 0) ? sc : DEFAULT_CAPACITY;@SuppressWarnings("unchecked")// 初始化数组 16个Node数组Node<K, V>[] nt = (Node<K, V>[]) new Node<?, ?>[n];// 赋值给table全局变量table = tab = nt;// 计算下次扩容的阈值 也就是*2操作 sc = n - (n >>> 2);}} finally {// 将最终的阈值设置给sizeCtlsizeCtl = sc;}break;}}return tab;}

添加数据-数组

数据添加到数组上(没有hash冲突)

final V putVal(K key, V value, boolean onlyIfAbsent) {for (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh;// 上面的逻辑就是初始化数组    // 通过 n-1 & hash 计算当前数据的索引位置 // f 其实就是对应位置的引用 如果这个位置为空,说明这个数组都没有添加过数据else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {// CAS的方式构建一个Node阶段,然后将hash值,key,value存储到Node中// 跳出if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null)))break;                   // no lock when adding to empty bin}// 这里说明f对应的hash为move 说明当前位置数据已经迁移到新数组。else if ((fh = f.hash) == MOVED)// 帮助扩容完。tab = helpTransfer(tab, f);}

添加数据-链表

final V putVal(K key, V value, boolean onlyIfAbsent) {// 拿到binCountint binCount = 0;for (Node<K,V>[] tab = table;;) {// n: 数组长度。 i:索引位置。  f:i位置的数据。 fh:是f的hash值Node<K,V> f; int n, i, fh;// 到这,说明出现了hash冲突,i位置有数据,尝试往i位置下挂数据else {V oldVal = null;//上面的逻辑如果都没有走到,那么说明出现了hash冲突, //先进行加syn 锁,注意这个锁的粒度是一个桶的粒度。synchronized (f) {// 再次判断if (tabAt(tab, i) == f) {// fhif (fh >= 0) {// binCount 赋值为1 记录链表中Node的长度binCount = 1;//e 指向数组位置数据 // 在遍历链表的过程中,会记录当前链表的个数for (Node<K,V> e = f;; ++binCount) {K ek;// 拿到当前hash值比对if (e.hash == hash &&((ek = e.key) == key ||(ek != null && key.equals(ek)))) {// 走到这里,说明桶中的有元素就冲突oldVal = e.val;// 如果是put方法,进去覆盖值// 如果是putIfAbsent,进去不if逻辑if (!onlyIfAbsent)e.val = value;break;}//走到这里。说明一直遍历完毕,说明桶中没有元素冲突。// 挂在链表的最后Node<K,V> pred = e;if ((e = e.next) == null) {// 封装成一个Node节点pred.next = new Node<K,V>(hash, key,value, null);break;}}}// 否则就是红黑树套路 else if (f instanceof TreeBin) {Node<K,V> p;binCount = 2;if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,value)) != null) {oldVal = p.val;if (!onlyIfAbsent)p.val = value;}}}}//binCount不为0 if (binCount != 0) {// 如果>= 8 那么进行扩容操作。if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i);if (oldVal != null)return oldVal;break;}}}return null;

触发扩容

    // 判断是否需要转红黑树 或者扩容 private final void treeifyBin(Node<K,V>[] tab, int index) {//N : 数组 sc:sizeCtlNode<K,V> b; int n, sc;if (tab != null) {// 数组长度小于64 不转红黑树  先扩容(将更多的数据落到数组上)//只有数组长度>= 64并且链表长度达到8 才转为红黑树if ((n = tab.length) < MIN_TREEIFY_CAPACITY)tryPresize(n << 1);// 转红黑树操作// 将单向链表转换为TreeNode对象(双向链表),再通过TreeBin方法转为红黑树。// TreeBin中保留着双向链表以及红黑树!else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {synchronized (b) {//.....}}}}

在这里插入图片描述

流程图

在这里插入图片描述

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

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

相关文章

DRM-VAE

&#x1d6ff; means variance&#xff0c;&#x1d443;(&#x1d44b;|&#x1d467;;&#x1d703;) means a function that make &#x1d467; close to &#x1d44b;. 作者未提供代码

Selenium 学习(0.19)——软件测试之基本路径测试法——拓展案例

1、案例 请使用基本路径法为变量year设计测试用例&#xff0c;year的取值范围是1000<year<2001。代码如下&#xff1a; 2、步骤 先画控制流程图 再转化为控制流图&#xff08;标出节点&#xff09; V(G) 总区域数 4 V(G) E - N 2 (边数 - 节点数 2…

【HarmonyOS4.0】第三篇-类web开发模式

【HarmonyOS4.0】第三篇-类web开发模式 一、鸿蒙介绍 课程核心 为什么我们需要学习鸿蒙&#xff1f; 哪些人适合直接转鸿蒙&#xff1f; 鸿蒙系统优势是什么&#xff1f; 课程内容 (1)为什么要学习鸿蒙 从行情出发&#xff1a; 美国商务部长访问中国&#xff0c;2023年…

文件上传至阿里云

注册阿里云账号后,开通好对象存储服务&#xff08;OSS&#xff09;&#xff0c;三个月试用 阿里云登录页 (aliyun.com) 目录 一.创建Bucket 二.获取AccessKey&#xff08;密钥&#xff09; 三.参考官方SDK文件&#xff0c;编写入门程序 1.复制阿里云OSS依赖&#xff0c;粘贴…

数模学习day10-聚类模型

说明&#xff0c;本文部分图片和内容源于数学建模交流公众号 目录 K-means聚类算法 K-means聚类的算法流程&#xff1a; 图解 算法流程图 评价 K-means算法 基本原则 算法过程 Spss软件操作 K-means算法的疑惑 系统&#xff08;层次&#xff09;聚类 算法流程 Sp…

设计模式之外观模式【结构型模式】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档> 学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某…

Camunda ServiceTask

一&#xff1a;Java class Java class实现JavaDelegate接口&#xff0c;只需要配置类的全限定名即可&#xff0c;不需要被Spring容器管理。 public class JavaClassServiceTask implements JavaDelegate {Overridepublic void execute(DelegateExecution execution) throws …

6.1 截图工具HyperSnap6简介

图片是组成多媒体作品的基本元素之一&#xff0c;利用图片可以增强多媒体作品的亲和力和说说服力。截取图片最简单的方法是直接按下键盘上的“PrintScreen”键截取整个屏幕或按下“AltPrintScreen”组合键截取当前活动窗口&#xff0c;然后在画笔或者其它的图片处理软件中进行剪…

Hyperledger Fabric 管理链码 peer lifecycle chaincode 指令使用

链上代码&#xff08;Chaincode&#xff09;简称链码&#xff0c;包括系统链码和用户链码。系统链码&#xff08;System Chaincode&#xff09;指的是 Fabric Peer 中负责系统配置、查询、背书、验证等平台功能的代码逻辑&#xff0c;运行在 Peer 进程内&#xff0c;将在第 14 …

业界首款PCIe 4.0/5.0多通道融合接口SSD技术解读

之前小编写过一篇文章劝大家不要碰PCIe 5.0 SSD&#xff0c;详细内容&#xff0c;可以再回顾下&#xff1a; 扩展阅读&#xff1a;当下最好不要入坑PCIe 5.0 SSD 如果想要进一步了解PCIe 6.0&#xff0c;欢迎点击阅读&#xff1a; 浅析PCIe 6.0功能更新与实现的挑战 PCIe 6.…

基于Springboot的计算机学院校友网(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的计算机学院校友网(有报告)。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通过Spring…

DDPM具体步骤

一、前向过程&#xff08;扩散过程&#xff09; 在前向过程中&#xff0c;模型逐步向数据添加噪声&#xff0c;直到数据完全转化为无结构的噪声 这个过程可以用马尔可夫链来描述&#xff0c;其中每一步都向数据添加一小部分高斯噪声 假设我们有一个初始数据分布 p(x0​)&…