阅读ConcurrentHashMap源码,我学到了什么?

news2024/11/25 11:47:49

文章目录

    • ConcurrentHashMap怎样保证线程安全的
    • put元素的流程
    • 具体对于红黑树是怎样保证线程安全的
    • 如何并发安全的初始化一个数组
    • 如何统计存储元素个数的
    • 怎样进行多线程扩容的

首先说明, 本篇分析基于jdk1.8.

ConcurrentHashMap怎样保证线程安全的

ConcurrentHashMap主要是通过Unsafe操作+synchronized关键字保证并发安全.
Unsafe操作主要负责并发安全的修改对象的属性或数组某个位置的值.
synchronized主要负责在需要操作某个位置时进行加锁. 这个位置不会为空,下面挂着一个链表或者红黑树.

put元素的流程

当向ConcurrentHashMap中put一个key,value时:

  1. 首先根据key计算对应的下标数组i,如果该位置没有元素,则通过自旋的方式向该位置赋值.
  2. 如果该位置与元素, 则会使用synchronized加锁.
  3. 加锁成功后,判断该元素的类型.
    (1).如果是链表就添加到链表节点中.
    (2).如果是红黑树就添加到红黑树中.
  4. 添加成功后,根据该位置元素个数判断是否需要转成红黑树.
  5. 利用LongAdder的思想统计添加元素个数
  6. 判断是否需要扩容, 需要的话进行多线程扩容.

具体对于红黑树是怎样保证线程安全的

根据key计算对应的数组下标位置时, 如果该位置是红黑树,需要通过synchronized锁住该位置的节点去保证线程安全.

但是我们要想象一个场景,如果我们要锁这个红黑树的话,就需要锁它的根节点, 但是在并发的情况下,我们知道红黑树存在左移右移这些操作,就可能导致它的根节点发生变化,所以就不能锁这个根节点.那应该怎样做呢?

在源码中,对于红黑树结构, 其实会封装成TreeBin,这样有什么好处呢?

这个时候我们我们就把这个红黑树看作一个整体,封装成一个TreeBin对象,直接锁这个对象,就可以解决这个问题.而根节点作为这个对象的一个属性,这样即使发生变化, TreeBin对象也不会变.

在这里插入图片描述

如何并发安全的初始化一个数组

ConcurrentHashMap内部结构依然是数组+链表+红黑树.

而在最开始我们 new ConcurrentHashMap<>();的时候,并不会初始化Node数组, 只有在put的时候, 发现数组为null,才会进行初始化.

那这个步骤也就会涉及到并发问题, 那如果是你你会怎么初始化这个数组.其实就容易想到的就是双IF单例模式.源码中并没有这样做,让我们一起看一看是怎样处理的.

源码中主要是依靠数值去区分的,也就是这个sc.初始化是正数.

首先所有某个线程通过CAS去修改这个sc,修改成-1,只会有一个线程修改成功, 这个成功的线程就会去真正的初始化这个数组.

而其他的失败线程会重新循环, 这个时候这个数值就是负数,代表有其他线程正在初始化这个数组.此时调用yield()方法,让出cpu.

当数组初始化完毕后,所有线程都退出循环.

在这里插入图片描述

如何统计存储元素个数的

在map中我们需要统计存储元素的个数, 这个也是并发的.那怎样保证这个线程安全呢?

如果是我们自己的话,大概率会直接使用AtomicInteger, 但是这个在高并发的情况下,效率并不算高,因为是针对一个变量进行CAS的,也就是假如有一百个线程在put,在进行到统计总数的时候,这些线程得串行的一个一个处理成功才行.

在ConcurrentHashMap底层是怎样做的?
这里其实用了一个分散的思想,就是如果针对一个变量进行计数的话,竞争很激烈,那就利用hash,将其分散到不同的变量中去, 最后再统计数量的时候,就把这些数量相加即可.

如下:
在源码中会有一个baseCount,还会有一个Cell数组.

在计数的时候, 会对baseCount利用CAS+1,如果失败,会hash到Cell数组中的某个位置,对该位置上的数值利用CAS+1. 总之就是不断尝试, 利用CAS对不同位置进行+1,直到执行成功.

在最后计算总和的时候,会把baseCount和Cell数组中的每一个都加起来返回.

在这里插入图片描述
那这里使用分散的思想,又引入了一个数组,其实又带来了很多问题.

  1. 这个Cell数组设置多大呢?
    不能设置的很大,因为如果在并发小的情况下,大了纯属浪费空间,应该最开始比较小,需要的话后面进行扩容.

  2. 那什么时候应该扩容呢?什么时候不应该扩容?
    (1).这个数组中的空值比较多不进行扩容
    (2).如果冲突少也不进行扩容
    怎么判断?
          连续两次尝试遇到的位置都不为空,并且都没有CAS成功,这个时候才进行扩容.

  3. 那数组的大小最大有没有什么限制?
    肯定是有最大大小限制的,不能无限扩容,源码中用的是cpu核心数.

  4. 这个数组是多线程操作的,怎么保证线程安全?
    其实用了一个变量CellBusy,代表是否繁忙, 就相当于一个锁,当有线程在操作该数组的时候,会利用CAS把CellBusy设置为1,这个时候其他线程就不能对数组进行操作.从而保证线程安全.

那这种方法优点就是,利用分散的思想,减少竞争,提高了效率,这个其实也就是LongAdder的思想,在并发比较高的情况,可以优先使用这个.

怎样进行多线程扩容的

其实大体流程和HashMap中的扩容差不多, 都是新建一个数组, 按下标位置进行移动.如果这里不太熟悉的话,可以先看一下这个文章HashMap源码详解.

区别有两点:

  1. 怎样保证线程安全?
    其实和它put元素时保证线程安全是一样的,直接对数组中的首节点Node进行synchronized加锁.

  2. 单线程扩容容易理解, 但在源码中为了提升效率会使用多线程扩容, 是怎样处理的呢?

    首先在进入扩容方法的瞬间会保证只有一个线程先进去,用CAS保证.

    其次这里是可以多线程扩容关键就是分配步长, 就比如一个数组长度是4, 那么就分配前两个节点由线程1进行扩容,后两个节点由线程2进行扩容.

    在分配步长的时候, 有两个关键变量, 就是即将分配的未来线程要处理的起始位置和末尾位置, 表示这个区间将由这个线程进行转移处理.

    通过不断维护更新这两个变量, 对不同线程进行分配不同的区间,从而进行多线程扩容.当然这个维护更新这两个变量的这个动作是用CAS保证线程安全的.

今天的分享就到这里了,有问题可以在评论区留言,均会及时回复呀.
我是bling,未来不会太差,只要我们不要太懒就行, 咱们下期见.
在这里插入图片描述

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

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

相关文章

Java:字符流

字符流的底层其实就是字节流。 字符流字节流字符集 结构体系&#xff1a; 1.特点 输入流:一次读一个字节&#xff0c;遇到中文时&#xff0c;一次读多个字节。 输出流:底层会把数据按照指定的编码方式进行编码&#xff0c;变成字节再写到文件中。 2.使用场景 对于纯文本…

AI实战营第二期 笔记5——MMPretrain代码课

文章目录 摘要MMPreTrain实战安装推理 OR 使用API数据集训练与测试微调 摘要 MMPretrain 是一个全新升级的预训练开源算法框架&#xff0c;旨在提供各种强大的预训练主干网络&#xff0c; 并支持了不同的预训练策略。MMPretrain 源自著名的开源项目 MMClassification 和 MMSel…

chatgpt赋能python:Python开立方函数math:让数学计算更加简单

Python开立方函数math&#xff1a;让数学计算更加简单 Python作为一种高级编程语言&#xff0c;提供了丰富的数学计算功能&#xff0c;其中就包括了开立方函数math。本篇文章将详细介绍math开立方函数在Python中的使用方法及其优点。 什么是开立方函数&#xff1f; 开立方函…

chatgpt赋能python:Python开发BI,助力企业数据驱动决策

Python开发BI&#xff0c;助力企业数据驱动决策 随着企业数据规模的呈现爆炸式增长&#xff0c;传统的Excel等表格计算工具已经无法满足业务需求&#xff0c;因此&#xff0c;企业需要更加全面和强大的方法来进行数据分析和展示。此时&#xff0c;BI&#xff08;全称Business …

css蓝桥杯--电影院排座位

目录 一、介绍二、准备三、⽬标四、代码五、知识点六、完成 一、介绍 随着⼈们⽣活⽔平的⽇益提升&#xff0c;电影院成为了越来越多的⼈休闲娱乐&#xff0c;周末放松的好去处。各个城市的电影院数量也随着市场的需求逐年攀升。近⽇&#xff0c;⼜有⼀个电影院正在做着开张前…

chatgpt赋能python:Python强制关闭程序的解决方案

Python 强制关闭程序的解决方案 在Python开发过程中&#xff0c;难免会遇到程序卡死&#xff0c;无响应等问题&#xff0c;这时候如果无法正常退出程序&#xff0c;就需要进行强制关闭。本篇文章将介绍Python强制关闭程序的几种解决方案。 方法一: 使用系统命令 在Linux或Ma…

【Java】JavaWEB核心要点总结:64

文章目录 1. TCP 和 UDP的异同2. TCP为什么要三次握手 两次不行吗3. get post put 请求方式有什么区别4. 什么是XXS攻击 如何避免5. 什么是 CSRF 攻击&#xff0c;如何避免 1. TCP 和 UDP的异同 TCP&#xff08;Transmission Control Protocol&#xff09;和UDP&#xff08;Use…

typedef 和 # define 用法区别

typedef 和 # define 用法区别 前言1. 原理不同1.1 typedef int * int_ptr;与#define int_ptr int * 详细讲解 2. 功能不同3. 作用域不同 前言 博主在牛客网上看到了一道有关typedef和# define题目。发现有很多初学的小伙伴对两者的用法不是特别清楚&#xff0c;所以博主在这总…

python内存

在python中&#xff0c;一切都是对象。Python从设计之初就是一门面向对象的语言&#xff0c;它有一个重要的概念&#xff0c;即一切皆对象。 Java虽然也是面向对象编程的语言&#xff0c;但是血统没有Python纯正。比如Java的八种基本数据类型之一int&#xff0c;在持久化的时候…

HCIA-NAT

目录 NAT&#xff1a;网络地址转换 NAT原理&#xff1a; NAT转换原理图&#xff1a; 静态NAT 静态NAT的工作原理&#xff1a; 静态NAT配置命令 静态NAT配置实例&#xff1a; 动态NAT 动态NAT的工作原理 动态NAT&#xff1a; 动态NAT配置命令 动态NAT案例 NAPT NA…

css蓝桥杯--⾃适应⻚⾯

目录 一、介绍二、准备三、⽬标四、代码五、完成 一、介绍 响应式布局是在 2010 年 5 ⽉份提出的⼀个概念&#xff0c;这个概念是为解决移动互联⽹浏览⽽诞⽣的。简⽽⾔之&#xff0c;就是⼀个⽹站能够兼容多个终端——⽽不是为每个终端做⼀个特定的版本。通过响应式布局可以为…

EXCEL文本处理总结:如何查找(/定位)字符串内,符合条件的多个符号里的最后一个?

目录 题外话&#xff1a;学习总结 1 新手切忌贪多 2 熟练者切忌懒惰 3 这2件事恰恰都和人性相反 1 EXCEL文本处理相关函数 2 查找函数 find() 和 search() 2.1 find() 2.2 search() 2.3 下面是测试的公式情况 3 如何查找(定位)符合条件的某个字符的位置&#xff1f;…

MySQL触发器的使用

目录 一、前言二、触发器分类1.插入触发器2.更新触发器3.删除触发器 三、查看触发器四、异常处理五、小结 一、前言 各种主流数据库,都集成了触发器的功能。触发器提供了一种机制,允许开发者在对数据库表的插入、更新、删除的前后捕获相应的数据行。从而针对数据行实现特定的逻…

DebugView的使用

目录 一、前言二、本机调试1.DebugView程序文件说明2.OutputDebugString函数使用3.示例程序4.远程调试 三、问题与注意事项四、小结 一、前言 DebugView是windows下的一款调试工具,可以捕获程序输出的日志,分为64位和32位,支持应用层和内核层的日志捕获,利用它排除bug是个不错的…

chatgpt赋能python:Python开发手机软件的优势和挑战

Python开发手机软件的优势和挑战 随着智能手机的普及&#xff0c;移动应用开发已成为当前最热门的技术领域之一。随着越来越多的企业意识到移动应用的重要性&#xff0c;越来越多的开发者开始加入这个领域。 在移动应用的开发中&#xff0c;由于其高效性和易于学习的特点&…

【Flutter】Dart/Flutter SDK如何降低版本、回退到指定版本

因为dart3.0以后不再支持 no-sound-null-safety&#xff1b;但是有些项目不得以切换到dart3.0以前继续使用运行项目 方法1&#xff1a; 通过 $ flutter downgrade命令&#xff0c;将flutter降级为当前通道的上一个活动版本&#xff1b; 如果没有存在老版本则会提示 flutter …

从零手写操作系统之RVOS软件定时器实现-08

从零手写操作系统之RVOS软件定时器实现-08 定时器分类软件定时器的分类软件定时器设计与实现软件定时器调用流程增加对周期性定时任务支持测试优化点 本系列参考: 学习开发一个RISC-V上的操作系统 - 汪辰 - 2021春 整理而来&#xff0c;主要作为xv6操作系统学习的一个前置基础。…

chatgpt赋能python:Python强制等待:如何优化你的Python技能

Python强制等待&#xff1a;如何优化你的Python技能 在Python编程中&#xff0c;强制等待是一种非常重要的程序设计方式。Python代码中的强制等待通常使用time.sleep()方法实现。在本文中&#xff0c;我们将详细介绍什么是Python强制等待&#xff0c;以及如何使用它来优化你的…

基于最近电平逼近的开环MMC逆变器MATLAB仿真模型

资源地址&#xff1a; 模型介绍&#xff1a; MATLAB21b版本 DC:12kV&#xff0c;N&#xff1d;12&#xff0c; 采用最近电平逼近调制&#xff0c;采用基于排序的均压方法&#xff0c;冒泡排序&#xff0b;桥臂电流方向判断。 连接负载&#xff0c;可以得到13电平相电压波形。…

Windows10下使用VS2019编译chromium

Windows10下使用VS2019编译chromium 工具设置代理cmd运行gclient配置VS的版本,环境变量设置下载源码生成编译工具 下载depot_tools,并配置环境变量,PATH下添加depot_tools的解压路径E:\src\depot_tools 设置代理 控制台管理员权限执行 git config --global http.proxy…