ConcurrentHashMap详解 什么时候CAS什么时候synchronized

jdk:1.7 segment数组+hashEntry数组+链表实现

jdk版本:1.8:hashEntry+数组+红黑树实现

1、基本参数

//**1、最大容量**  hashmap的最大容量也是这个,菜鸟一面被问到了
private static final int MAXIMUM_CAPACITY = 1 << 30;//数组默认为16
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;//float浮点数,LOAD_FACTOR为final 不可更改
private static final float LOAD_FACTOR = 0.75f;

2、初始化1

也就是说不是你输入什么就是什么容量,内部还有一个算法优化用户的输入。(防止用户输入参数太差)  
//逻辑:如果init>max的一半,则返回max,否则返回tableSizeFor(init + (initialCapacity >>> 1) + 1)); 
public ConcurrentHashMap(int init) {if (init < 0)throw new IllegalArgumentException();int cap = ((init >= (MAXIMUM_CAPACITY >>> 1)) ?MAXIMUM_CAPACITY :tableSizeFor(init + (init >>> 1) + 1));this.sizeCtl = cap;    //`sizeCtl` 表示容量
}//c=2返回2,c=3返回4,c=9返回16
//tableSizeFor 方法通过一系列位操作,将输入值 c 转换为大于或等于 c 的最小 2 的幂次方。这是为了确保哈希表的数组大小总是 2 的幂次方,从而优化哈希分布和查找性能。private static final int tableSizeFor(int c) {int n = c - 1;n |= n >>> 1;n |= n >>> 2;n |= n >>> 4;n |= n >>> 8;n |= n >>> 16;return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

初始化2

 public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel) {if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)throw new IllegalArgumentException();if (initialCapacity < concurrencyLevel)   // Use at least as many binsinitialCapacity = concurrencyLevel;   // as estimated threads//非常神奇啊,传入的loadFactor不会改变装填因子,但是会改变传入的容量参数,影响最终容量long size = (long)(1.0 + (long)initialCapacity / loadFactor);int cap = (size >= (long)MAXIMUM_CAPACITY) ?MAXIMUM_CAPACITY : tableSizeFor((int)size);this.sizeCtl = cap;}

3、sizeCtl 不太理解

sizeCtlConcurrentHashMap 中一个非常重要且复杂的控制字段,其作用有很多个。

addCount()里面判断是否要扩容是和sizeCtl比较,而不是和装填因子比较。为什么使用 sizeCtl 而不是直接使用负载因子?

  1. 简化计算
    • 通过将负载因子的计算隐式地包含在 sizeCtl 中,可以避免每次插入或删除元素时重新计算负载因子,从而减少了计算开销。
  2. 并发控制
    • 使用 sizeCtl 可以有效地协调多个线程同时进行扩容操作。负值表示正在扩容,并且包含扩容的状态信息。这样,可以避免多个线程重复触发扩容。
  3. 性能优化
    • 在高并发环境下,频繁访问和修改共享变量(如负载因子)会带来性能瓶颈。通过使用 sizeCtl,可以减少这种共享变量的访问次数,从而提高性能。
// Unsafe mechanicsprivate static final long SIZECTL;
//初始化map时候,this.sizectl = cap; //cap为容量//比如public ConcurrentHashMap(Map<? extends K, ? extends V> m) {this.sizeCtl = DEFAULT_CAPACITY;putAll(m);}在扩容期间:当扩容开始时,sizeCtl 被设置为一个负值,表示当前参与扩容的线程数量。这样其他线程可以知道表正在扩容并可以协同进行扩容操作。

4、put 重点

public V put(K key, V value) {return putVal(key, value, false);}/** Implementation for put and putIfAbsent */final V putVal(K key, V value, boolean onlyIfAbsent) {//1、数据检查if (key == null || value == null) throw new NullPointerException();//2、求key哈希int hash = spread(key.hashCode());int binCount = 0;  //记录遍历的节点数,可以用于判断是否要链表转化为红黑树for (Node<K,V>[] tab = table;;) {  //死循环Node<K,V> f; int n, i, fh;if (tab == null || (n = tab.length) == 0)  //检查 table 是否初始化tab = initTable();//使用哈希值计算索引 i 并检查该位置是否为空。如果为空,使用 CAS 操作插入新节点,并跳出循环。else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null)))break;                   // no lock when adding to empty bin}else if ((fh = f.hash) == MOVED) // MOVEDd标志用于判断是否已经节点迁移//当一个桶(bin)中的所有节点都被迁移到新的数组中后,原来的位置上会放置一个特殊的转发节点,表示这个桶已经处理完毕。此时,转发节点的 hash 字段会被设置为 MOVED(即 -1)。tab = helpTransfer(tab, f);//协助迁移else {   //如果碰撞了  需要使用synchronized,放弃cas,f是table那个碰撞节点V oldVal = null;synchronized (f) {if (tabAt(tab, i) == f) { // 经典的双重检查,防止当前线程获取table锁之前,tabAt(tab, i)被其它线程改变了if (fh >= 0) {// 哈希值>=0代表是链表,<0代表是红黑树binCount = 1;for (Node<K,V> e = f;; ++binCount) {K ek;if (e.hash == hash &&((ek = e.key) == key ||(ek != null && key.equals(ek)))) {  // 三比较,hashcode==hashcode,key==key,key.equals(key)oldVal = e.val;if (!onlyIfAbsent)e.val = value;break;}Node<K,V> pred = e;if ((e = e.next) == null) {pred.next = new Node<K,V>(hash, key,value, null);break;}}}else if (f instanceof TreeBin) { // 红黑树Node<K,V> p;binCount = 2;  //binCount 被初始化为 2,因为红黑树中的节点数计算方式不同于链表。 具体原因我不知道if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,value)) != null) {oldVal = p.val;if (!onlyIfAbsent)p.val = value;}}}}if (binCount != 0) {   // 判断是否要扩容 TREEIFY_THRESHOLD=8if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i);if (oldVal != null)return oldVal;break;}}}addCount(1L, binCount);  //计数 里面通过cas维护元素个数。return null;}

总结:

  1. 判断key value是否合法 判null
  2. 求key哈希
  3. 判断table是否初始化,如果没有就初始化
  4. 找到key哈希在table中的位置,判断是否为null
    1. 如果是Null则cas直接添加节点
    2. 如果碰撞了 需要使用synchronized(f){},放弃cas,f是table那个碰撞节点
      1. 如果f.hash>=0,则说明是链表,遍历查找,当(hashhash && keykey && key.equals(key))的时候返回元素。下一个节点为null还没有找到,则插入节点
      2. 如果f.hash<0, 则说明是红黑树,遍历查找。找不到就插入。
    3. 判断是否要转化为红黑树
  5. 调用addCount()计算map总的元素个数,内部通过cas来实现。
    1. 这里面会检查table要不要扩容

**5、remove(Object key) **和put差不多

 final V replaceNode(Object key, V value, Object cv) {int hash = spread(key.hashCode());for (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh;if (tab == null || (n = tab.length) == 0 ||(f = tabAt(tab, i = (n - 1) & hash)) == null)    // 空直接返回 没得删break;else if ((fh = f.hash) == MOVED)tab = helpTransfer(tab, f);else {          //不空则锁起来 再遍历 找到就删V oldVal = null;boolean validated = false;synchronized (f) {if (tabAt(tab, i) == f) {if (fh >= 0) {  // hash>=0 链表validated = true;for (Node<K,V> e = f, pred = null;;) {K ek;if (e.hash == hash &&((ek = e.key) == key ||(ek != null && key.equals(ek)))) {V ev = e.val;if (cv == null || cv == ev ||(ev != null && cv.equals(ev))) {oldVal = ev;if (value != null)e.val = value;else if (pred != null)pred.next = e.next;elsesetTabAt(tab, i, e.next);}break;}pred = e;if ((e = e.next) == null //找不到break;}}else if (f instanceof TreeBin) {validated = true;TreeBin<K,V> t = (TreeBin<K,V>)f;TreeNode<K,V> r, p;if ((r = t.root) != null &&(p = r.findTreeNode(hash, key, null)) != null) {V pv = p.val;if (cv == null || cv == pv ||(pv != null && cv.equals(pv))) {oldVal = pv;if (value != null)p.val = value;else if (t.removeTreeNode(p))setTabAt(tab, i, untreeify(t.first));}}}}}if (validated) {     // 计数if (oldVal != null) {if (value == null)addCount(-1L, -1);return oldVal;}break;}}}return null;}

6、get(Object key)

public V get(Object key) {// 定义一些局部变量Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;// 计算键的哈希值,并进行哈希值扩散以减少碰撞int h = spread(key.hashCode());// 如果哈希表不为空并且哈希表长度大于0if ((tab = table) != null && (n = tab.length) > 0 &&// 计算哈希表索引,取出对应位置的节点(e = tabAt(tab, (n - 1) & h)) != null) {// 如果找到的节点的哈希值等于目标哈希值if ((eh = e.hash) == h) {// 并且键也相等(引用相等或 equals 相等),则返回该节点的值if ((ek = e.key) == key || (ek != null && key.equals(ek)))return e.val;}// 如果节点的哈希值小于0,表示该节点是一个特殊节点(如红黑树节点或转移节点)else if (eh < 0)// 调用 find 方法在该节点中查找目标键对应的值return (p = e.find(h, key)) != null ? p.val : null;// 否则,遍历该链表中的所有节点while ((e = e.next) != null) {if (e.hash == h &&((ek = e.key) == key || (ek != null && key.equals(ek))))return e.val;}}// 如果没有找到,返回 nullreturn null;
}

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

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

相关文章

《科技与健康》是什么级别的期刊?是正规期刊吗?能评职称吗?

问题解答 问&#xff1a;《科技与健康》期刊万方网可查吗 答&#xff1a;万方、维普可查 问&#xff1a;《科技与健康》是正规期刊吗&#xff1f; 答&#xff1a;万方维普收录的正规期刊。主管单位&#xff1a;长江出版传媒股份有限公司 主办单位&#xff1a;湖北科学技术…

孩子出生后为什么要做听力筛查?

孩子出生后为什么要做听力筛查&#xff1f; 新生儿听力筛查&#xff0c;就是对所有新生儿在尽早的时间&#xff08;出生48小时后&#xff09;进行系统的听力筛查测试。据相关文献报道&#xff0c;在我国&#xff0c;正常分娩的新生儿听力障碍的发生率约为0.1&#xff5e;0.3%&a…

机场专用手持激光驱鸟器原理及优势

在机场的驱鸟工作中&#xff0c;各类驱鸟设备共同构建起一道坚不可摧的防线&#xff0c;以保障航班的安全起降。其中激光驱鸟器以其卓越的性能和显著效果&#xff0c;在机场鸟击防治中发挥着至关重要的作用。 激光驱鸟器&#xff0c;分为大型自动式和小型手持式&#xff0c;其有…

Python 技能提升(二)

理想的类结构 Property装饰器 # 传统写法 class Square1:def __init__(self):self.__side Nonedef get_side(self):return self.__sidedef set_side(self, side):assert side > 0, 边长不能为负数&#xff01;self.__side sidedef del_side(self):# del self.__sideself.…

「前端+鸿蒙」核心技术HTML5+CSS3(十)

1、H5简介 H5是HTML5的简称,是构建现代网站和网络应用的标准标记语言。HTML5新增了许多功能,包括更好的多媒体支持、新的表单控件、绘图功能以及对响应式设计的改进。 2、H5产品布局 移动端H5网站布局通常使用流体布局或弹性盒模型(Flexbox),以适应不同屏幕尺寸。 示例…

2024年有什么值得入手的5G长期套餐大流量卡推荐?大流量手机卡入手指南(超4款正规手机卡实测总结)

前言 24年有什么值得入手的5G大流量卡推荐&#xff1f;大流量手机卡入手指南&#xff08;超4款正规手机卡实测总结&#xff09; 四大运营商有哪些大流量卡&#xff0c;可电话&#xff0c;非物联网卡 所有卡激活后&#xff0c;均可以在官方app可查、 所有都是优惠长期 5G大流…

Python-匿名函数

一、概念 匿名函数造出来的是一个内存地址&#xff0c;且内存地址没有绑定任何名字&#xff0c;很快被当做垃圾清理掉。所以匿名函数只需要临时调用一次&#xff0c;而有名函数永久使用&#xff1b; 匿名函数一般和其他函数配合使用&#xff1b; # 有名函数def func(x, y):…

抖音直播统计、直播间无人互动直播效果软件--抖音大师!

抖音大师介绍 抖音大师是抖音直播统计、直播间无人互动直播效果软件&#xff0c;通过它&#xff0c;你可以快速添加直播互动效果&#xff01;软件使用C#开发&#xff0c;无论是内存占用还是执行效果都远比同行的效果高太多&#xff01;&#xff01;电脑所需性能大大降低&#x…

内联汇编简介

在C语言中嵌入汇编&#xff08;Assembly&#xff09;代码&#xff0c;可以使用内联汇编&#xff08;Inline Assembly&#xff09;&#xff0c;这在一些需要精确控制硬件或者优化性能的场合非常有用 以下是关于ASM语法的介绍&#xff0c;主要基于GCC&#xff08;GNU Compiler C…

做软件测试需要懂代码吗?

随着大数据、机器学习时代的到来&#xff0c;不少人有了“测试不需要懂代码&#xff0c;那我就试试”的想法。这就引发了一系列疑问&#xff1a;不懂代码可以做测试吗&#xff1f;测试人员到底需不需要懂代码&#xff1f;测试人员需要写代码吗&#xff1f; 其实&#xff0c;在…

精准检测,可燃气体报警系统的技术原理与特点

在现代化的工业生产与日常生活中&#xff0c;可燃气体泄露事故频发&#xff0c;给人们的生命和财产安全带来了严重威胁。 因此&#xff0c;可燃气体报警检测系统的应用变得尤为重要。它不仅能够实时监测环境中的可燃气体浓度&#xff0c;还能在发现异常情况时及时报警&#xf…

记 Codes 开源免费研发管理平台 —— 生成式全局看板的创新实现

继上一回合瀑布与敏捷的融合创新实现后&#xff0c;本篇我们来讲一讲 Codes 生成式全局看板的创新实现。 市面上所有的研发管理软件&#xff0c;看板模式的项目&#xff0c;都是物理看板的电子化&#xff0c;好像也没什么问题&#xff0c;但是在使用过程中体验非常不好&#xf…

WebSocket和HTTP协议对比

WebSocket和HTTP是两种不同的通信协议&#xff0c;它们在多个方面存在显著差异&#xff0c;主要区别包括&#xff1a; 通信模式&#xff1a; HTTP 是一种无状态的、基于请求-响应模型的协议。这意味着通信总是由客户端发起请求&#xff0c;服务器被动响应。每次请求和响应都是独…

使用 zxing 生成二维码以及条形码

需求背景 前期在做项目的时候&#xff0c;有一个需求是说要生成一张条形码&#xff0c;并且呢将条形码插入到 excel 中去&#xff0c;但是之前一直没有搞过找个条形码或者是二维码&#xff0c;最后是做出来了&#xff0c;这里呢就先看看怎么生成&#xff0c;后面再抽时间来写写…

一条SQL语句的执行究竟经历了哪些过程

在数据库管理系统(DBMS)中,一条SQL语句的执行过程复杂且精细,从用户输入到获取结果,中间需要经过多个步骤和组件的协同工作。这些步骤包括解析、优化、执行和结果返回等。以下是SQL语句执行过程的详细分析: 1. 客户端连接 连接建立: 用户通过客户端(如应用程序、SQL客户…

掌握Element UI:加速你的网页设计过程!

Element UI 是一套为开发者、UI/UX设计师和产品经理准备的采用Vue 2.0作为基础框架实现的组件库&#xff0c;提供配套的设计资源&#xff0c;可以帮助设计快速成型。即时设计也内置Element UI Kit资源&#xff0c;但有些小伙伴还是对此不太了解&#xff0c;接下来本文会详细带你…

antd-vue - - - - - a-select结合i18n使用(踩坑问题)

antd-vue - - - - - a-select结合i18n使用&#xff08;踩坑问题&#xff09; 1. 当前代码 & 效果2. 解决办法 1. 当前代码 & 效果 <a-selectv-model:value"formState.quickSwitching":options"quickSwitchingOptions"search"handleSearch…

vue3+element-plus 表单校验和循环form表单校验

1.HTML页面 //el-form 标签添加上 ref"form2Form" :rules"rules2" :model"form2" 正常表单校验 //没有循环表单的使用事例<el-form-item label"投保人名称" class"insurance-date-no1" prop"tbrName">…

什么是增值税通俗的理解

增值税的目的是为了对商品或服务在生产过程中增加的价值进行征税。通俗地说&#xff0c;就是每当商品或服务在生产和销售过程中“增值”了一次&#xff0c;政府就要对这部分增值收税。 举个例子&#xff0c;假设一个农场主种了小麦&#xff0c;然后卖给了面粉厂。面粉厂将小麦加…

29、亲身体验Young GC风暴:模拟教程带你走进GC的神秘世界!

29.1、前文回顾 在今天的文章,我们将通过代码演示来展示年轻代的Young GC是如何发生的。同时,我们还将指导大家如何在JVM参数中配置打印对应的GC日志。接下来,我们将通过分析GC日志,逐步解析JVM的垃圾回收机制是如何运作的。 29.2、不可不知的JVM参数设置技巧 首先,根据…