并发永远是高性能的话题,而并发容器又是java中重要的并发工具,所以今天我们来分析一下Concurrent包中ConcurrentHashMap(以下简称Chashmap)。普通容器在某些并发情况下的表现很差,假设这容器的体积很大,容器获得锁后进行了非常耗时的遍历操作,那么锁就会被占用很长世间。事实上只有很小一部分元素被线程占用,其余的元素完全可以被读写。而(以下简称Chashmap就是实现了这一特性,使用该容器一部分区域的时候其他线程可以同时读写容器中的剩余区域,所以Chashmap的效率很高。

Chashmap的关键实现是一种分段锁机制,简单来讲就是将hash表分成一段段子表,分别加锁。这样线程需要占用哪一段就获取哪一段的锁,剩余的段不受影响。本质上Chashmap和hashMap没什么区别,元素都是Entry,一个节点加链表。发生冲突的时候采用链表法存储元素,所以源码里会有大量的通过key的hash值找到槽后进行遍历查询的操作。下面是子表的声明:

自定义了一个segment类,继承了可重入锁。可以看一下整体的结构:

其中比较关键的应该是put,rehash,scanandlock,remove,replace等方法。

我们先来看segment的put方法:

对map中的某个segment做put操作,加入我们来实现一般会考虑到几点:

需要加锁,因为要考虑到key相同的情况,如果不加锁,两个线程同时更新同一个key的value,会出错。

1)需要考虑锁已经被占的情况。

2)需要更新segment的元素个数。

3)需要考虑segment扩容的问题。

带着这些考虑,我们再来看源码可能就会更有针对性一些。

第一步果然是加锁,这里直接用了trylock,因为segment继承了可重入锁,其实这是个很垃圾的设计,按理来说应该优先使用组合而不是继承。我们可以看到,尝试获取锁失败后,调用了scanAndLockForPut方法,这个方法会不断尝试获取锁,同时去匹配entry的key值,直到成功,重新回到put方法。Put方法里考虑到了扩容的问题:

假如元素个数大于阈值,会调用rehash方法。Rehash首先进行了数组的扩容:

同时对老的元素进行迁移的时候进行了rehash操作: idx = e.hash&sizeMask

最后put方法会更新元素总数:

下面我们来看一下remove方法:

1)同样的我们首先来自己思考一下哪些点是需要考虑到的:

2)需要枷锁,同时考虑锁被抢占,同插入

3)更新元素个数

首先是尝试获取锁,如果锁被占了,那么等待获取。

查找的时候先获取Entry,然后遍历链表:

找到元素后进行普通的链表移除元素操作,并更新元素总数。

这里有点奇怪的地方,modeCount为什么加一了?这点后面解答。

Replace和remove操作几乎一致,这里不再赘述。

看完segment的主要方法我们再来仔细看一下Chashmap的方法,首先是put方法:

首先要获取对应的segment,然后委托给具体的segment。这里有个细节,对于value为null的情况抛出了异常。

对于remove方法同样也是委托执行:

'

从上述代码可以看出put和remove方法都是委托给底层的segment执行,所以不复杂,倒是size这种需要统计的方法因为要考虑到并发更改元素的情况会比较复杂。Size方法需要考虑几点

统计过程中各个segment的元素个数可能会被动态改变的情况怎么检测,如何处理?

不断重试统计的标准是什么?即什么时候停止重试?

假设重试次数过多应该采取什么策略?

我们看到size的代码可以看出上述3个疑问的解答:

  1. 通过检测两次的modecount的和是否相等来检测统计期间是否有并发线程改变了segment的元素个数
  2. 重试次数达到一个阈值(2次)锁定所有的segment
  3. 分别读取各个segment的元素个数加和获得总的元素个数

对于其他类似的读取操作,比如contains操作,也会有在读取期间发生元素被添加/移除的情况。采取的策略也是一致的,读取两次并对比,如果不一致就加锁。

最后我们来看一下get方法:


没什么特别的地方,就是一个定位查询。

版权声明:本文为博主原创文章,未经博主允许不得转载。

【JDK源码系列】ConcurrentHashMap的更多相关文章

  1. JDK源码系列总索引

    一 目标 记录学习jdk源码的一些笔记和心得,jdk版本使用11.0.1,工具idea Class后面序号为优先级1-4,优先级递减 目录转载自博客: https://blog.csdn.net/qq ...

  2. 深入学习JDK源码系列之、ArrayList

    前言 JDK源码解析系列文章,都是基于JDK8分析的,虽然JDK15马上要出来了,但是JDK8我还不会,我... 类图 实现了RandomAccess接口,可以随机访问 实现了Cloneable接口, ...

  3. HashSet源码分析:JDK源码系列

    1.简介 继续分析源码,上一篇文章把HashMap的分析完毕.本文开始分析HashSet简单的介绍一下. HashSet是一个无重复元素集合,内部使用HashMap实现,所以HashMap的特征耶继承 ...

  4. JDK源码系列(一) ------ 深入理解SPI机制

    什么是SPI机制 最近我建了另一个文章分类,用于扩展JDK中一些重要但不常用的功能. SPI,全名Service Provider Interface,是一种服务发现机制.它可以看成是一种针对接口实现 ...

  5. jdk源码->集合->ConcurrentHashMap

    类的属性 public class ConcurrentHashMap<K,V> extends AbstractMap<K,V> implements ConcurrentM ...

  6. JDK源码学习系列05----LinkedList

                                             JDK源码学习系列05----LinkedList 1.LinkedList简介 LinkedList是基于双向链表实 ...

  7. JDK源码学习系列04----ArrayList

                                                                             JDK源码学习系列04----ArrayList 1. ...

  8. JDK源码学习系列03----StringBuffer+StringBuilder

                         JDK源码学习系列03----StringBuffer+StringBuilder 由于前面学习了StringBuffer和StringBuilder的父类A ...

  9. JDK源码学习系列02----AbstractStringBuilder

     JDK源码学习系列02----AbstractStringBuilder 因为看StringBuffer 和 StringBuilder 的源码时发现两者都继承了AbstractStringBuil ...

随机推荐

  1. 广州大学华软软件学院——NA视频下载

    准备工具: 360极速浏览器(不要认错图标了): 浏览器视频下载插件: 第一步:安装浏览器插件 1.打开浏览器 2.解压,找到插件文件: 3.把插件拖到浏览器中: 4.添加,然后就完成了插件安装 查看 ...

  2. Android应用正确使用扩展SD卡,特别是安卓4.4以后的版本

    Android 开发时如何正确获取使用扩展存储路径 在 2.x 版本中,Android设备都是单存储,第三方App写文件,必须申请 WRITE_EXTERNAL_STORAGE 权限: 在4.0之后, ...

  3. Altium Designer导出部分元件过滤不焊接的元件【worldsing笔记】

    在Altium Designer画图过程中难免会多出单元电路,或是测试电路,特别是第一版时,有部分元件不用焊接   这时给采购.或是生产时有必要注明哪些元件不焊接,哪些元件不采购.有两种方法可以过滤: ...

  4. FreeModbus 精简版本(Only RTU) for M128 (Modbus Slave)

    1.硬件连接: M128 PD3 INT3/TXD1  ---> PC 232COM Pin 2 PD2 INT2/RXD1  ---> PC 232COM Pin 3 GND       ...

  5. Android中的动画学习总结

    android中动画可分为三种:帧动画,补间动画,和属性动画.其中属性动画是google推荐的,它可以实现前面两种动画的效果,运用起来更加灵活. 帧动画:顾名思义,就是一帧一帧的图片,快速播放形成的动 ...

  6. 获取Application中的spring容器

    方式1: ApplicationContext ac = (ApplicationContext) invocation.getInvocationContext().getApplication() ...

  7. nape.geom.MarchingSquares

    Nape中的MarchingSquares类很简单,只有一个静态函数run,不过这对绘制那些简单的形状来说,已经足够了(当然MarchingSquares能做的不只这些).下面是这个run方法的结构: ...

  8. Spring 从零開始-03

    这里说说bean装配集合.spring的支持的集合元素,其基本使用方式如同与Java的集合,所以假设对Java的集合不太了解的能够先找个帖子好好学习一下, 时间关系这里就不说了. ~~ list的样例 ...

  9. SQL Sever——无法连接到(local)。“未配置远程连接”和“请求失败或服务未及时响应”

       攻克了上篇博客提到的"远程过程调用失败(0x800706be)"的问题. 新的问题接踵而至. . . 一.       watermark/2/text/aHR0cDovL2 ...

  10. #define和预编译指令

    今天再总结一点#define和预处理指令的使用. 预处理过程扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器.可见预处理过程先于编译器对源代码进行处理. 预处理指令是以#开头的代码行,#后是 ...