一、容器初始化

1、源码分析

在jdk8的concurrenthashmap中一共有5个构造方法,这四个构造方法中都没有对内部的数组做初始化, 只是对一些变量的初始值做了处理

jdk8的concurrenthashmap的数组初始化是在第一次添加元素时完成

注意,调用这个方法,得到的初始容量和hashmap以及jdk7的concurrenthashmap不同,即使你传递的是一个2的幂次方数,该方法计算出来的初始容量依然是比这个值大的2的幂次方数

2、sizectl含义解释

注意:以上这些构造方法中,都涉及到一个变量sizectl,这个变量是一个非常重要的变量,而且具有非常丰富的含义,它的值不同,对应的含义也不一样,这里我们先对这个变量不同的值的含义做一下说明,后续源码分析过程中,进一步解释

sizectl为0,代表数组未初始化, 且数组的初始容量为16

sizectl为正数,如果数组未初始化,那么其记录的是数组的初始容量,如果数组已经初始化,那么其记录的是数组的扩容阈值

sizectl为-1,表示数组正在进行初始化

sizectl小于0,并且不是-1,表示数组正在扩容, -(1+n),表示此时有n个线程正在共同完成数组的扩容操作

3、其他属性含义

代表整个哈希表

用于哈希表扩容,扩容完成后会被重置为null。

basecount和countercells一起保存着整个哈希表中存储的所有的结点的个数总和。

二、添加安全

1、源码分析

1.1、添加元素put/putval方法

通过以上源码,我们可以看到,当需要添加元素时,会针对当前元素所对应的桶位进行加锁操作,这样一方面保证元素添加时,多线程的安全,同时对某个桶位加锁不会影响其他桶位的操作,进一步提升多线程的并发效率

1.2、数组初始化,inittable方法

2、图解

2.1、put加锁图解

三、扩容安全

1、源码分析

2、图解

四、多线程扩容效率改进(协助扩容)

多线程协助扩容的操作会在两个地方被触发:

① 当添加元素时,发现添加的元素对用的桶位为fwd节点,就会先去协助扩容,然后再添加元素

② 当添加完元素后,判断当前元素个数达到了扩容阈值,此时发现sizectl的值小于0,并且新数组不为空,这个时候,会去协助扩容

每当有一个线程帮助扩容时,sc就会+1,有一个线程扩容结束时,sc就会-1,当sc重新回到(rs << resize_stamp_shift) + 2这个值时,代表当前线程是最后一个扩容的线程,则扩容结束。

1、源码分析

1.1、元素未添加,先协助扩容,扩容完后再添加元素

1.2、先添加元素,再协助扩容

注意:扩容的代码都在transfer方法中,这里不再赘述

2、图解

五、集合长度的累计方式

1、源码分析

1.1、addcount方法

① countercell数组不为空,优先利用数组中的countercell记录数量

② 如果数组为空,尝试对basecount进行累加,失败后,会执行fulladdcount逻辑

③ 如果是添加元素操作,会继续判断是否需要扩容

1.2、fulladdcount方法

① 当countercell数组不为空,优先对countercell数组中的countercell的value累加

② 当countercell数组为空,会去创建countercell数组,默认长度为2,并对数组中的countercell的value累加

③ 当数组为空,并且此时有别的线程正在创建数组,那么尝试对basecount做累加,成功即返回,否则自旋

2、图解

fulladdcount方法中,当as数组不为空的逻辑图解

六、集合长度获取

1、源码分析

1.1、size方法

1.2、sumcount方法

注意:这个方法并不是线程安全的

七、get方法

这个就很简单了,获得hash值,然后判断存在与否,遍历链表即可,注意get没有任何锁操作!

到此这篇关于jdk1.8中的concurrenthashmap源码分析的文章就介绍到这了,更多相关jdk1.8 concurrenthashmap源码内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!