首页 > 其他分享 >ConcurrentHashMap底层详解

ConcurrentHashMap底层详解

时间:2024-03-21 20:58:34浏览次数:36  
标签:扩容 ConcurrentHashMap HashMap 阈值 链表 详解 线程 数组 底层

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为当前集合的数组。

标签:扩容,ConcurrentHashMap,HashMap,阈值,链表,详解,线程,数组,底层
From: https://blog.csdn.net/m0_60469045/article/details/136920447

相关文章

  • nmon监控工具使用方法详解
    在信息化时代,系统监控对于确保服务器和应用的稳定运行至关重要。nmon(Nigel’sMonitor)作为一款强大的性能监控工具,以其直观、全面的监控能力,赢得了众多系统管理员的青睐。本文将详细介绍nmon监控工具的使用方法,帮助读者更好地利用这一工具,提升系统监控效率。一、nmon监控工......
  • JavaScript初识及基本语法详解
    JavaScript是一种轻量级的解释型或即时编译型的编程语言。它最初被设计为在浏览器中用于与网页进行交互,但随着时间的推移,它已经成为了后端开发、游戏开发、桌面应用开发等多个领域的重要工具。1.JavaScript初识1.1历史与用途历史:由BrendanEich在1995年开发,最初......
  • 多线程并发聊天室简单实现代码详解 -- 涉及网络编程,多线程和线程同步的知识
            本项目主要完成多线程并发聊天室的基础功能,即多个客户端之间通过服务器可以实现群发消息,重点在于学习网络编程,多线程和线程同步的基础知识(基于Linux)。    下面我会详解每一部分的代码。1.主线程        1.1首先由于是自己在电脑里面测试,......
  • B树B+树,字典树详解,哈夫曼树博弈树
    目录B树:B-TreeB+树字典树:TrieTree 哈夫曼树博弈树B树:B-Tree多路平衡搜索树1.M阶B树,就是M叉(M个指针)。2.每个节点内记录个数<=M-1。3.根节点记录个数>=1。4.其余节点内记录个数>=ceil(m/2)-1(向上取整)。5.每个节点内的记录从左至右从大到小有序。6.当前记录的左......
  • Impostors详解——纸片构筑的美丽幻觉
    【USparkle专栏】如果你深怀绝技,爱“搞点研究”,乐于分享也博采众长,我们期待你的加入,让智慧的火花碰撞交织,让知识的传递生生不息!写在前面早前,我截帧分析了《CallofDragons》,具体内容可以看《〈CallofDragons〉渲染截帧分析与迷思》,在其中提到了《CallofDragons》中使用......
  • Linux系统下的文件描述符fd详解
    文章目录文件描述符本作者从代码及源码的角度来总结探究文件描述符fd参考:韦东山Linux嵌入式视频文件描述符Linux系统下一切皆文件。文件描述符是操作系统中用来唯一标识一个已打开文件的整数。本质上来说就是索引,即根据索引值寻找到对应的文件,可对其进行相应......
  • Java基础内容:第七章面向对象(下)编程题详解
            建了一个群:908722740 ,欢迎小伙伴门的加入,平时可以互相学习交流,不管是学习还是工作上的都可以多多交流,本人在校学生,技术有限,错误的地方欢迎指正。目录一、多态案例素材【1】乐手弹奏乐器【2】比萨制作【3】购买饮料二、接口案例素材【1】兔子和青蛙【......
  • RCC时钟代码详解<一步一注释>
    voidSystemClock_Config(void){RCC_OscInitTypeDefRCC_OscInitStruct={0};RCC_ClkInitTypeDefRCC_ClkInitStruct={0};/**Configurethemaininternalregulatoroutputvoltage*/__HAL_RCC_PWR_CLK_ENABLE();__HAL_PWR_VOLTAGESCALING_CONFIG(PW......
  • 开源计算机视觉库OpenCV详解
    开源计算机视觉库OpenCV是一个功能强大的工具,用于实现各种计算机视觉应用。以下是对OpenCV的详细解释和使用示例:一、功能概述OpenCV涵盖了广泛的计算机视觉领域,包括但不限于以下功能:图像处理:包括图像加载、保存、调整大小、旋转、裁剪、滤波、边缘检测等。OpenCV提供了丰富......
  • 集合系列(十) -Set接口详解
    一、摘要关于Set接口,在实际开发中,其实很少用到,但是如果你出去面试,它可能依然是一个绕不开的话题。言归正传,废话咱们也不多说了,相信使用过Set集合类的朋友都知道,Set集合的特点主要有:元素不重复、存储无序的特点。啥意思呢?你可以理解为,向一个瓶子里面扔东西,这些东西......