一盏茶的时间了解下concurrentHashmap

目录

JDK1.7

hash计算

1.7扩容

Segement【0】原型

JDK1.8

JDK1.8扩容

 扩容数据迁移问题


JDK1.7

采用Segement数组(蓝色)+HashEntry数组(绿色)+链表(头插法)的方式实现

new的同时创建了Segement数组(饿汉式)

capacity:数组容量(绿色)(小数组的)

factor:负载因子(绿色),影响小数组,不影响Segment数组

clevel:并发度(Segment数组并发度一旦指定就固定了),当并发度为16,Segment数组大小就为16,当并发度为8,Segment数组大小就为8,并发度为8如图:

小数组初始容量为:capacity / clevel,最低为2

 如上图:小数组初始容量为32 / 8 = 4;

hash计算

 线程一如图:鼠标指针所在 二次hash后转换为二进制后高四位1100计算得出索引为 :12(二的三次方+二的二次方)

为什么是高四位? 因为Segment数组并发度clevel为16 = 2的四次方

所以Segment下标为12,put后如图,放在了Segment下标为12的数组下面:

线程2计算hash:

 如图看出clevel为32 所以求二次hash转换为二进制的高5位(因为2的5次方为32)

得出索引:20,如图:b放在了Segment数组下标为20的地方

 计算了Segment数组的下标,那么有同学肯定有疑问了,小数组下标怎么计算呢?

答案:小数组下标是由最低位计算得出的

1.7扩容

Segment数组不会扩容。HashEntry数组(小数组)才会扩容

当元素个数超过3/4时扩容

扩容的小数组各不影响

链表头插法解决hash冲突(这里是故意让hash值一样产生hash冲突),不会引起死链,因为concurrentHashmap线程安全(每个线程操作时都加锁了)

Segement【0】原型

在Segment数组创建好后,只有Segment下标为0的地方建好了小数组

当其他小数组添加元素时会以Segment下标为0的小数组为原型设置容量大小和负载因子

JDK1.8

concurrentHashmap采用数组+链表+红黑树的方式实现

当put元素时才会创建数组 (懒汉式)

JDK1.8扩容

当数组容量满3/4时扩容 (和负载因子无关)

1.8的concurrenthashmao的capacity 和factor 只用于初始化数组的容量,后面就和这俩参数无关了

capacity:初始要放多少元素

如果capacity为16,则代表要放16个元素,数组得多大呢,显然得32,如果为16则放不下16个元素(因为当数组容量满3/4时扩容

当capacity为11时 数组容量才为16(为12时等于16的3/4(0.75),扩容了)

 当capacity为 11,factor 为 0.5时发现数组容量又为32了,因为11超过了16*0.5 = 8了

 扩容数据迁移问题

扩容:当数组元素到达了阈值创建一个新的数组,将旧数据迁移过去

 红色的F代表一个参数:forwardingNode

在扩容复制数据时,处理完了一个链表,旧的链表头要替换成forwardingNode,代表这个链表处理完了,其他线程发现forwadingNode旧知道这个链表被处理了,旧不会重复处理

 如上图所示:当所有结点都迁移完成,旧数组中链表投都变成了forwardingload结点,代表扩容完成了,最后用新的数组替换旧的数组。

结点下标位置:重新hash计算

猜你喜欢

转载自blog.csdn.net/m0_60264772/article/details/122951654