iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > VUE >如何进行ConcurrentHashMap内部实现
  • 890
分享到

如何进行ConcurrentHashMap内部实现

2024-04-02 19:04:59 890人浏览 泡泡鱼
摘要

如何进行ConcurrentHashMap内部实现,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。ConcurrentHashMap可以说是

如何进行ConcurrentHashMap内部实现,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

如何进行ConcurrentHashMap内部实现

ConcurrentHashMap可以说是目前使用最多的并发数据结构之一,作为如此核心的基本组件,不仅仅要满足我们功能的需求,更要满足性能的需求。而实现一个高性能的线程安全的HashMap也绝非易事。

ConcurrentHashMap作为jdk8的内部实现,一个成功的典范,有着诸多可以让我们学习和致敬的地方。

我全局在项目中搜索这个类的时候,发现大量项目代码和源码都用到了,为什么他会这么吃香呢?到底是道德的....呸。

下面我们就来扒一扒,ConcurrentHashMap的内部实现,来体会一下它的精妙之处吧!

ConcurrentHashMap的内部数据结构

在JDK8中,  ConcurrentHashMap的内部实现发生了天翻地覆的变化。这里依据JDK8,来介绍一下ConcurrentHashMap的内部实现。

从静态数据结构上说,ConcurrentHashMap包含以下内容:

int sizeCtl

这是一个多功能的字段,可以用来记录参与Map扩展的线程数量,也用来记录新的table的扩容阈值

CounterCell[] counterCells

用来记录元素的个数,这是一个数组,使用数组来记录,是因为避免多线程竞争时,可能产生的冲突。使用了数组,那么多个线程同时修改数量时,极有可能实际操作数组中不同的单元,从而减少竞争。

node<K,V>[] table

实际存放Map内容的地方,一个map实际上就是一个Node数组,每个Node里包含了key和value的信息。

Node<K,V>[] nextTable

当table需要扩充时,会把新的数据填充到nextTable中,也就是说nextTable是扩充后的Map。

以上就是ConcurrentHashMap的核心元素,其中最值得注意的便是Node,Node并非想象中如此简单,下面的图展示了Node的类族结构:

如何进行ConcurrentHashMap内部实现

可以看到,在Map中的Node并非简单的Node对象,实际上,它有可能是Node对象,也有可能是一个Treebin或者ForwardingNode。

那什么时候是Node,什么时候是TreeBin,什么时候又是一个ForwardingNode呢?

其实在绝大部分场景中,使用的依然是Node,从Node数据结构中,不难看出,Node其实是一个链表,也就是说,一个正常的Map可能是长这样的:

如何进行ConcurrentHashMap内部实现

上图中,绿色部分表示Node数组,里面的元素是Node,也就是链表的头部,当两个元素在数据中的位置发生冲突时,就将它们通过链表的形式,放在一个槽位中。

当数组槽位对应的是一个链表时,在一个链表中查找key只能使用简单的遍历,这在数据不多时,还是可以接受的,当冲突数据比较多少,这种简单的遍历就有点慢了。

因此,在具体实现中,当链表的长度大于等于8时,会将链表树状化,也就是变成一颗红黑树。如下图所示,其中一个槽位就变成了一颗树,这就是TreeBin(在TreeBin中使用TreeNode构造整科树)。

 如何进行ConcurrentHashMap内部实现

当数组容量快满时,即超过75%的容量时,数组还需要进行扩容,在扩容过程中,如果老的数组已经完成了复制,那么就会将老数组中的元素使用ForwardingNode对象替代,表示当前槽位的数据已经处理了,不需要再处理了,这样,当有多个线程同时参与扩容时,就不会冲突。

put()方法的实现

现在来看一下作为一个HashMap最为重要的方法put():

  • public V put(K key, V value)

它负责将给定的key和value对存入HashMap,它的工作主要有以下几个步骤:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 如果没有初始化数组,则尝试初始化数组

  3. 如果当前正在扩容,则参与帮助扩容(调用helpTransfer()方法)

  4. 将给定的key,value 放入对应的槽位

  5. 统计元素总数

  6. 触发扩容操作

根据以上主要4个步骤,来依次详细说明一下:

如果没有初始化数组,则尝试初始化数组

初始化数据会生成一个Node数组:

Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];

默认情况下,n为16。同时设置sizeCtl为&middot;n - (n >>> 2);  这意味着sizeCtl为n的75%,表示Map的size,也就是说ConcurrentHashMap的负载因子是0.75。(为了避免冲突,Map的容量是数组的75%,超过这个阈值,就会扩容)

如果当前正在扩容,则参与帮助扩容

else if ((fh = f.hash) == MOVED)     tab = helpTransfer(tab, f);

如果一个节点的hash是MOVE,则表示这是一个ForwardingNode,也就是当前正在扩容中,为了尽快完成扩容,当前线程就会参与到扩容的工作中,而不是等待扩容操作完成,如此紧密细致的操作,恰恰是ConcurrentHashMap高性能的原因。

而代码中的f.hash==MOVE语义上等同于f instanceof  ForwardingNode,但是使用整数相等的判断的效率要远远高于instanceof,所以,这里也是一处对性能的极限优化

将给定的key,value 放入对应的槽位

在大部分情况下,应该会走到这一步,也就是将key和value放入数组中。在这个操作中会使用大概如下操作:

Node<K,V> f; synchronized (f) {      if(所在槽位是一个链表)          插入链表      else if(所在槽位是红黑树)          插入树      if(链表长度大于8[TREEIFY_THRESHOLD])          将链表树状化 }

可以看到,这使用了synchronized关键字,住了Node对象。由于在绝大部分情况下,不同线程大概率会操作不同的Node,因此这里的竞争应该不会太大。

并且随着数组规模越来越大,竞争的概率会越来越小,因此ConcurrentHashMap有了极好的并行性。

统计元素总数

为了有一个高性能的size()方法,ConcurrentHashMap使用了单独的方法来统计元素总数,元素数量统计在CounterCell数组中:

CounterCell[] counterCells; @sun.misc.Contended static final class CounterCell {     volatile long value;     CounterCell(long x) { value = x; } }

CounterCell使用伪共享优化,具有很高的读写性能。counterCells中所有的成员的value相加,就是整个Map的大小。这里使用数组,也是为了防止冲突。

如果简单使用一个变量,那么多线程累加一个计数器时,难免要有竞争,现在分散到一个数组中,这种竞争就小了很多,对并发就更加友好了。

累加的主要逻辑如下:

if (as == null || (m = as.length - 1) < 0 ||     //不同线程映射到不同的数组元素,防止冲突     (a = as[ThreadLocalRandom.getProbe() & m]) == null ||     //使用CAS直接增加对应的数据     !(uncontended =       U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)))     //如果有竞争,在这里会重试,如果竞争严重还会将CounterCell[]数组扩容,以减少竞争

触发扩容操作

最后,ConcurrentHashMap还会检查是否需要扩容,它会检查当前Map的大小是否超过了阈值,如果超过了,还会进行扩容。

ConcurrentHashMap的扩容过程非常巧妙,它并没有完全打乱当前已有的元素位置,而是在数组扩容2倍后,将一半的元素移动到新的空间中。

所有的元素根据高位是否为1分为low节点和high节点:

//n是数组长度,数组长度是2的幂次方,因此一定是100 1000 10000 100000这种二进制数字 //这里将low节点串一起, high节点串一起 if ((ph & n) == 0)     ln = new Node<K,V>(ph, pk, pv, ln); else     hn = new Node<K,V>(ph, pk, pv, hn);

接着,重新放置这些元素的位置:

//low节点留在当前位置 setTabAt(nextTab, i, ln); //high节点放到扩容后的新位置,新位置距离老位置n setTabAt(nextTab, i + n, hn); //扩容完成,用ForwardingNode填充 setTabAt(tab, i, fwd);

下图显示了 从8扩充到16时的可能得一种扩容情况,注意,新的位置总是在老位置的后面n个槽位(n为原数组大小)

如何进行ConcurrentHashMap内部实现

这样做的好处是,每个元素的位置不需要重新计算,进行查找时,由于总是会对n-1(一定是一个类似于1111 11111  111111这样的二进制数)按位与,因此,high类的节点自然就会出现在+n的位置上。

get()方法的实现

与put()方法相比,get()方法就比较简单了。步骤如下:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 根据hash值 得到对应的槽位 (n - 1) & h

  3. 如果当前槽位第一个元素key就和请求的一样,直接返回

  4. 否则调用Node的find()方法查找

  5. 对于ForwardingNode 使用的是 ForwardingNode.find()

  6. 对于红黑树 使用的是TreeBin.find()

  7. 对于链表型的槽位,依次顺序查找对应的key

ConcurrentHashMap可以说是并发设计的典范,在JDK8中,ConcurrentHashMap可以说是再一次脱胎换骨,全新的架构和实现带来了飞一般的体验(JDK7中的ConcurrentHashMap还是采用比较骨板的segment实现的),细细品读,还是有不少的收获。

关于如何进行ConcurrentHashMap内部实现问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注编程网VUE频道了解更多相关知识。

--结束END--

本文标题: 如何进行ConcurrentHashMap内部实现

本文链接: https://www.lsjlt.com/news/81676.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 如何进行ConcurrentHashMap内部实现
    如何进行ConcurrentHashMap内部实现,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。ConcurrentHashMap可以说是...
    99+
    2024-04-02
  • 如何进行C#中Dictionary的内部实现分析
    本篇文章为大家展示了如何进行C#中Dictionary的内部实现分析,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。了解Dictionary的开发人员都了解,和List相比,字典添加会慢,但是查找会比...
    99+
    2023-06-17
  • 如何进行Flutter仿头条顶部tab切换实现
    这期内容当中小编将会给大家带来有关如何进行Flutter仿头条顶部tab切换实现,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。概述今天主要实现一个仿头条顶部tab切换实现,这种效果在项目中同样经常用到, ...
    99+
    2023-06-04
  • 如何进行MySQL update数据时InnoDB内部的操作
    这篇文章给大家介绍如何进行MySQL update数据时InnoDB内部的操作,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。 当MySQL更新数据时,In...
    99+
    2024-04-02
  • 使用Java如何实现ConcurrentHashMap#put并发容器
    使用Java如何实现ConcurrentHashMap#put并发容器?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。jdk1.7.0_79HashMap可以说是每个Java...
    99+
    2023-05-31
    java 并发容器
  • Java中ConcurrentHashMap是如何实现线程安全
    目录语法: ConcurrentHashmap 的需要:如何使 ConcurrentHashMap 线程安全成为可能?Hashtable、Hashmap、ConcurrentHash...
    99+
    2024-04-02
  • 网站内部结构的优化究竟该如何进行
    这篇文章给大家介绍网站内部结构的优化究竟该如何进行,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。 网站结构如何优化涉及以下六个部分: 1、栏目设计 分析:网站栏目的设计,在前面市场调查和竞争对手分析时,就已经...
    99+
    2023-06-12
  • 如何进行TCP通信实现
    本篇文章给大家分享的是有关如何进行TCP通信实现,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。TCP是底层通讯协议,定义的是数据传输和连接方式的规范。TCP协议,传输控制协议(...
    99+
    2023-06-05
  • VBS字符串如何在内部实现
    小编给大家分享一下VBS字符串如何在内部实现,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!VBS 是基于微软的 ActiveX/COM 技术实现的,而 COM 对...
    99+
    2023-06-08
  • 如何进行zabbix监控部署
    今天就跟大家聊聊有关如何进行zabbix监控部署,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。zabbix监控  环境 centos6.7...
    99+
    2024-04-02
  • MariaDB如何进行集群部署
    在MariaDB中进行集群部署通常使用Galera Cluster来实现。Galera Cluster是一个同步多主集群解决方案,可...
    99+
    2024-04-09
    MariaDB
  • 如何实现在一个时间段内进行间隔查询
    这篇文章主要介绍“如何实现在一个时间段内进行间隔查询”,在日常操作中,相信很多人在如何实现在一个时间段内进行间隔查询问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何实现在一...
    99+
    2024-04-02
  • 如何在Java2中实现匿名内部类
    这篇文章给大家分享的是有关如何在Java2中实现匿名内部类的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。public class TestMoto2 {public sta...
    99+
    2023-06-02
  • 如何深入Python字典的内部实现
    本篇文章给大家分享的是有关如何深入Python字典的内部实现,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。字典是通过键(key)索引的,因此,字典也可视作彼此关联的两个数组。下...
    99+
    2023-06-17
  • MySQL内部如何实现读锁和写锁
    这篇文章主要为大家展示了“MySQL内部如何实现读锁和写锁”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“MySQL内部如何实现读锁和写锁”这篇文章吧。对于MyS...
    99+
    2024-04-02
  • 如何进行Hybris UI的Route实现
    如何进行Hybris UI的Route实现,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。登录Hybris前台,在product catalog里选择Digita...
    99+
    2023-06-04
  • 如何进行实现Python的配置
    今天就跟大家聊聊有关如何进行实现Python的配置,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。在Python配置中的一个文本区域,其中某个名字空间可以直接访问,“直接访问” 这里指...
    99+
    2023-06-17
  • 如何深入Python列表的内部实现
    这篇文章给大家介绍如何深入Python列表的内部实现,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。Python 中的列表非常强大,看看它的内部实现机制是怎么样的,一定非常有趣。下面是一段 Python 脚本,在列表中添...
    99+
    2023-06-17
  • 如何进行 11.2.0.4 DG for linux 部署
    如何进行 11.2.0.4  DG for linux 部署,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。 ...
    99+
    2024-04-02
  • 如何进行C++内存管理?
    如何进行C++内存管理C++是一种强大的编程语言,但是它也要求开发者负责内存管理。在C++中,内存管理是非常重要的,因为错误的内存使用可能导致内存泄漏、野指针和其他一系列问题。因此,对于C++开发者来说,掌握良好的内存管理技巧至关重要。C+...
    99+
    2023-11-02
    内存分配 (Allocation) 内存释放 (Deallocation) 智能指针 (Smart Pointers)
软考高级职称资格查询
推荐阅读
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作