ConcurrentHashMap底层详解

ConcurrentHashMap是线程安全且高效的HashMap。

一、使用原因

在并发编程中使用HashMap可能导致程序死循环。而使用线程安全的HashTable效率又非常低下,基于此产生了ConcurrentHashMap。

1.线程不安全的HashMap

在多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不建议使用HashMap.

【HashMap在并发执行put操作时,引起死循环的原因是:多线程会导致HashMap的Entry链表形成环形数据结构,一旦形成环形数据结构,Entry的next节点永远不为空,就会产生死循环,获取Entry】

2.效率低下的HashTable

HashTable用synchronized来保证线程安全,但在线程激烈的情况下HashTable的效率很低,因为当一个线程访问Hashtable的同步方法,其他线程也访问HashTable同步方法的同时,会进入阻塞或轮询状态。

【在线程激烈的情况下HashTable的效率很低的原因:所有访问HashTable的线程必须竞争同一把锁】

3.ConcurrentHashMap的锁分段技术

假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效提高并发访问效率,这就是锁分段技术。

【将数据分成一段一段地存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问】

二、ConcurrentHashMap的结构

底层数据结构:

  • JDK1.7底层采用分段的数组+链表实现

  • JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。

(1)JDK1.7

  • 提供了一个segment数组,在初始化ConcurrentHashMap的时候可以指定数组的长度,默认是16,一旦初始化后中间不可扩容。

  • 在每个segment中都可以挂一个HashEntry数组,数组里面可以存储具体的元素,HashEntry数组是可以扩容的

  • 在HashEntry存储的数组中存储的元素,若发生冲突,则可以挂单向链表。

  • 先去计算key的hash值,然后确定segment数组下标

  • 再通过hash值确定hashEntry数组中的下标存储数据

  • 在进行操作数据的之前,会先判断当前segment对应下标位置是否有线程进行操作,为了线程安全使用的是ReentrantLock进行加锁,如果获取锁是被会使用cas自旋锁进行尝试

(2) JDK1.8

采用 CAS + Synchronized来保证并发安全进行实现

  • CAS控制数组节点的添加

  • synchronized只锁定当前链表或红黑二叉树的首节点,只要hash不冲突,就不会产生并发的问题 , 效率得到提升

总结:

  • 在jdk1.7中 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一 种数组和链表结构,一个 Segment 包含一个HashEntry 数组,每个 HashEntry 是一个链表结构 的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。

  • Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个HashEntry 数组里得元 素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 锁

  • 在jdk1.8中的ConcurrentHashMap 做了较大的优化,性能提升了不少。首先是它的数据结构与jdk1.8的hashMap数据结构完全一致。其次是放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保 证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲 突,就不会产生并发 , 效率得到提升

三、扩容机制

HashMap:

【负载因子为什么是0.75,这个在源码中有一段注释说明了,大致意思就是在时间和空间成本权衡而来,太小的值会浪费大部分空间,太大的值会增加get和put等操作的查找成本,还有根据泊松分布计算后得出的结论】

在JDK1.7的扩容机制相对简单,有以下特质:

  • 空参数的构造函数:以默认容量、默认负载因子、默认阈值初始化数组。内部数组是空数组。

  • 有参构造函数:根据参数确定容量、负载因子、阈值等。

  • 第一次put时会初始化数组,其容量变为不小于指定容量的2的幂数。然后根据负载因子确定阈值。

  • 如果不是第一次扩容,则 新容量=旧容量X2 新阈值=新容量X负载因子

JDK1.8下的**HashMap的容量变化通常存在以下几种情况**:

  • 空参数的构造函数:实例化的HashMap默认内部数组是null,即没有实例化。第一次调用put方法时,则会开始第一次初始化扩容,长度为16。

  • 有参构造函数:用于指定容量。会根据指定的正整数找到不小于指定容量的2的幂数,将这个数设置赋值给阈值(threshold)。第一次调用put方法时,会将阈值赋值给容量,然后让 阈值=容量X负载因子(因此并不是指定了容量就一定不会触发扩容,超过阈值后一样会扩容!!)

  • 如果不是第一次扩容,则容量变为原来的2倍,阈值也变为原来的2倍。

【注:1.7 是大于阈值(threshold = factor * capacity )且没有空位时才扩容,而 1.8 是大于阈值就扩容;1.7是先扩容再插入数据,1.8是先插入数据再扩容;HashMap的容量达到2的30次方,就不会再进行扩容了】

    ConcurrentHashMap的扩容条件和hashmap一样,集合内元素达到阈值或者链表长度达到8时扩容,不同的是CHM是线程安全的,支持多线程扩容,考虑的更多,也更为复杂。在JDK1.8中 CHM采用了CAS(CompareAndSwap,比较置换)+ synchronized 的方法来保证并发安全。CAS主要用到的主要属性sizeCtl,sizeCtl默认为0,当sizeCtl为负数时代表正在扩容或者初始化,当sizeCtl为正数时代表当前集合的扩容阈值,table为当前集合的数组。

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

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

相关文章

【软考】耦合

目录 一、说明二、7种耦合2.1 无直接耦合2.2 数据耦合2.3 标记耦合2.4 控制耦合2.5 外部耦合2.6 公共耦合2.7 内容耦合 三、耦合性与独立性四、例题4.1 例题1 一、说明 1.耦合是模块之间的相对独立性(互相连接的紧密程度)的度量 2.耦合取决于各个模块之间…

贾志杰“大前端”系列著作出版发行

杰哥著作《VueSpringBoot前后端分离开发实战》2021年出版以来,累计发行2.6万册,受到广大读者热捧。后应读者要求,受出版社再次邀请,“大前端”系列之《剑指大前端全栈工程师》、《前端三剑客》由清华大学出版社陆续出版发行。系列…

23.合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1: 输入:l1 [1,2,4], l2 [1,3,4] 输出:[1,1,2,3,4,4]示例 2: 输入:l1 [], l2 [] 输出:[]示…

蓝桥杯刷题(十三)

1.煤球数目 代码 cnt ans 0 start 1 a [] while cnt<100:ansstartstart 1t ansstartcnt1a.append(ans) print(sum(a))2.奖券数目 代码 def f(x)->bool:while x:if x%104:return Falsex//10return True ans 0 for i in range(10000,100000):if f(i):ans1 print(a…

24计算机考研调剂 | (研究所)北京微电子技术研究所

北京微电子技术研究所2024年考研调剂信息 调剂信息 一、招生专业 二、调剂对象 统考科目为思想政治理论、英语&#xff08;一&#xff09;、数学&#xff08;一&#xff09;&#xff1b;本科为电子科学与技术、微电子学、集成电路设计、电子信息工程、通信工程、计算机科学与…

聊聊 IO

聊聊IO 1、I/O&#xff1a;Input/Output 磁盘 IO&#xff1a;读写磁盘文件 网络 IO&#xff1a;读写 socket 文件 标准输入输出&#xff1a;读写控制台 2、阻塞 IO 应用进程发起 read() 请求之后&#xff0c;调用recvfrom()阻塞&#xff0c;直到数据进入数据接收队列。 数…

C++命名规则

如果想要有效的管理一个稍微复杂一点的体系&#xff0c;针对其中事物的一套统一、带层次结构、清晰明了的命名准则就是必不可少而且非常好用的工具。 活跃在生物学、化学、军队、监狱、黑社会、恐怖组织等各个领域内的大量有识先辈们都曾经无数次地以实际行动证明了以上公理的…

本地项目文件夹创建python文件并配置conda环境的完整流程

1 在Pycharm中创建新项目 位置就是本地的项目文件夹 2 接着打开pycharm的终端 创建conda环境&#xff08;这个过程需要保证conda.exe能够被系统路径识别&#xff09; conda create --name my_environment&#xff08;my_environment取自己想要的环境名字&#xff09; 还可以指…

java 两个Dto对象有字段数据不一致可Objects.equals方法却返回了一致?(问题分析与结论)

java 两个Dto对象有字段数据不一致可Object.equals方法却返回了一致&#xff1f;&#xff08;问题分析与结论&#xff09; 问题描述 以示意代码非原业务代码描述解释 基础类DownloadCenterBaseParam 子类 DashBoardQueryDto 新建两个对象并比对equals public static voi…

误删了Linux系统的libm.so.6文件与libm-2.27.so的软链接导致的开机出现kernel panic的解决方案(图文U盘救援详细教程)

事情起因 最近在做嵌入式视觉&#xff0c;捣弄rknn3588&#xff0c;在推理过程中报了一个错&#xff0c;就是说我的GLIBC的版本太低了&#xff0c;我也没有多想&#xff0c;想着升一下版本就好了&#xff0c;然后找到了这篇博客。【请谨慎操作】Ubuntu18.04升级GLIBC_2.29&…

【一起学Rust | 基础篇】rust线程与并发

文章目录 前言一、创建线程二、mpsc多生产者单消费者模型1.创建一个简单的模型2.分批发送数据3. 使用clone来产生多个生产者 三、共享状态&#xff1a;互斥锁1. 创建一个简单的锁2. 使用互斥锁解决引用问题 前言 并发编程&#xff08;Concurrent programming&#xff09;&#…

8节点空间壳单元Matlab有限元编程 | 曲壳单元 | 模态分析 | 3D壳单元 | 板壳理论| 【源代码+理论文本】

专栏导读 作者简介&#xff1a;工学博士&#xff0c;高级工程师&#xff0c;专注于工业软件算法研究本文已收录于专栏&#xff1a;《有限元编程从入门到精通》本专栏旨在提供 1.以案例的形式讲解各类有限元问题的程序实现&#xff0c;并提供所有案例完整源码&#xff1b;2.单元…