目录
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计算