【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.mzph.cn/news/609140.shtml

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

相关文章

银联扫码第三方支付接口申请:开启便捷支付新时代

随着移动支付的普及&#xff0c;越来越多的商家开始接受微信、支付宝等第三方支付平台的付款方式。然而&#xff0c;作为国内最大的银行卡组织&#xff0c;银联也在不断拓展其业务范围&#xff0c;推出了自己的扫码支付接口。本文将为您详细介绍银联扫码第三方支付接口的申请流…

GO语言笔记3-指针

指针的概念 先看一段代码的输出 package main import "fmt" func main(){ var age int 18fmt.Println("age的内存地址值是:",&age)//age的内存地址值是: 0xc000012090// 定义一个指针变量// *int 是一个指针类型&#xff0c;可以理解为指向int类型的…

两个视频怎么合并成一个视频?教你合并视频

两个视频怎么合并成一个视频&#xff1f;如果你是一名视频爱好者&#xff0c;或者是一名自媒体创作者&#xff0c;那么你一定遇到过需要将两个视频合并为一个的情况。有时候&#xff0c;你可能需要将一个长视频切割成多个片段&#xff0c;或者将多个视频片段合并成一个完整的视…

Spring MVC的RequestMapping注解、controller方法返回值

1.使用说明 作用&#xff1a;用于建立请求URL和处理请求方法之间的对应关系。 出现位置&#xff1a; 类上&#xff1a; 请求 URL的第一级访问目录。此处不写的话&#xff0c;就相当于应用的根目录。写的话需要以/开头。它出现的目的是为了使我们的 URL 可以按照模块化管理&…

我的1827创作纪念日

机缘 习惯性早上打开电脑&#xff0c;看看CSDN上的资讯&#xff0c;了解行业动态、当前新的技术和大佬的分享。自己动手写应该是2019 年 01 月 08 日&#xff0c;当时应该是在用安装和使用Oracle&#xff0c;遇到一些问题&#xff0c;写下第一篇博客 Oracle存储过程常见问题及…

一、Mybatis 简介

本章概要 简介持久层框架对比快速入门&#xff08;基于Mybatis3方式&#xff09; 1.1 简介 https://mybatis.org/mybatis-3/zh/index.html MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投G…

【解决方案】 无法将“pip“项识别为 cmdlet、函数、脚本文件

在当今的软件开发和运维领域&#xff0c;Python已经成为了一个不可或缺的工具。而pip&#xff0c;作为Python的包管理工具&#xff0c;更是Python生态系统中不可或缺的一部分。然而&#xff0c;有时候我们可能会遇到一个令人困扰的问题&#xff1a;无法将“pip”项识别为cmdlet…

zookeeper 与eureka区别

CAP定理 在分布式系统的发展中&#xff0c;影响最大的莫过于CAP定理了&#xff0c;是分布式系统发展的理论基石。 2000年&#xff0c;加州大学的计算机科学家 Eric Brewer提出了CAP猜想 2002 年&#xff0c;麻省理工学院的 Seth Gilbert 和 Nancy Lynch 从理论上证明了 CAP 猜…

c++实现支持动态扩容的栈(stack)

1.在栈容量满时自动扩容: 支持自动扩容栈实现: // // myStack.hpp // algo_demo // // Created by Hacker X on 2024/1/9. //#ifndef myStack_hpp #define myStack_hpp #include <stdio.h> #include <string.h> //栈实现 //1.入栈 //2.出栈 //3.空栈 //4.满栈 …

栈的模拟实现

栈的模拟实现 一:什么是栈二:IStack 接口三:MyStack类:1:push(int x):2:pop()3:peek()4:size(),empty(),full() 三:四:栈的时间复杂度: 一:什么是栈 栈是以先进后出(后进先出)的形式来组织数据结构 比如: 先装入的子弹后射出,后装入的子弹先射出,这就是一种典型的栈. 二:ISta…

扩展欧几里得算法总结

知识概览 裴蜀定理&#xff1a;对于任意正整数a&#xff0c;b&#xff0c;一定存在非零整数x&#xff0c;y&#xff0c;使得 而且(a, b)是a和b能凑出来的最小的正整数。 通过扩展欧几里得算法可以求得裴蜀定理中x和y的值&#xff0c;x和y的通解为 &#xff0c; 例题展示 扩展欧…

ChatGPT扩展系列之网易数帆ChatBI

在当今数字化快速发展的时代,数据已经成为业务经营与管理决策的核心驱要素。无论是跨国大企业还是新兴创业公司,正确、迅速地洞察数据已经变得至关重要。然而,传统的BI工具往往对用户有一定的技术门槛,需要熟练的操作技能和复杂的查询语句,这使得大部分的企业员工难以深入…

2023,半路转行程序员的第一年

键盘敲着总结&#xff0c;抬头看桌面的日期&#xff0c;转眼间来到了 2024 年&#xff0c;时间就这么悄悄的流逝。本来想 12 月底就把总结给写完的&#xff0c;结果一拖&#xff0c;拖到了 2024&#x1f602;。 我本科专业是材料&#xff0c;当时属于生环化材“天坑”专业&…

QT DAY1作业

1.QQ登录界面 头文件代码 #ifndef MYWIDGET_H #define MYWIDGET_H#include <QWidget> #include <QIcon> #include <QLabel> #include <QPushButton> #include <QMovie> #include <QLineEdit>class MyWidget : public QWidget {Q_OBJECTpu…

nn网络层-卷积层

一、1d/2d/3d Convolution 卷积运算&#xff1a;卷积核在输入信号&#xff08;图像&#xff09;上滑动&#xff0c;相应位置上进行乘加卷积核&#xff1a;又称为滤波器&#xff0c;过滤器&#xff0c;可认为是某种模式&#xff0c;某种特征。卷积过程类似于用一个模版去图像上…

将Llama2上下文长度扩展100倍;效率更高的SeTformer;LLM准确度基本不变加速1.56×;FreeTalker

本文首发于公众号&#xff1a;机器感知 将Llama2上下文长度扩展100倍&#xff1b;效率更高的SeTformer&#xff1b;LLM准确度基本不变加速1.56&#xff1b;FreeTalker Latte: Latent Diffusion Transformer for Video Generation 本文使用Latent Diffusion Transformer(Latte…

JS入门笔记整理:函数

函数一般用来实现某种重复使用的功能&#xff0c;在需要使用该功能的时候&#xff0c;直接调用函数就可以了&#xff0c;不需要再重复地编写一大堆代码。并且在需要修改该函数功能的时候&#xff0c;也只需要修改和维护这一个函数就行。函数一般会在两种情况下使用&#xff1a;…

Git(1):Git概述

1 开发中的实际场景 场景一&#xff1a;备份 小明负责的模块就要完成了&#xff0c;就在即将Release之前的一瞬间&#xff0c;电脑突然蓝屏&#xff0c;硬盘光荣牺牲&#xff01;几个月来的努力付之东流 场景二&#xff1a;代码还原 这个项目中需要一个很复杂的功能&#x…

【重学C语言】一、C语言简介

【重学C语言】一、C语言简介 什么是编程语言&#xff1f;编程语言 C语言发展史C语言标准变迁开发软件CLion安装步骤 VIsual Studio安装步骤 Clion 和 VS2022 绑定 电脑常识 什么是编程语言&#xff1f; 人类语言&#xff1a;语言就是人类进行沟通交流的表达方式&#xff0c;应…

26 数字验证

效果演示 实现了一个简单的数字密码输入表单&#xff0c;用户需要输入一个4位数字密码来验证身份。表单包含一个标题、描述、输入字段、两个按钮和一个关闭按钮。输入字段是一个4位数字密码&#xff0c;用户需要在每个输入框中输入数字来输入密码。两个按钮分别是“验证”和“清…