简单来说,HashMap就是一个Entry对象的数组。数组中的每个条目元素都是链表的头节点。
Hashmap不是线程安全的。在高并发环境中插入时,可能会出现以下循环链表:
在并发哈希映射集中有多少这样的段对象?有2的n次方,它们一起存储在一个名为段的数组中。
因此,整个并发哈希表的结构如下:
不同段的写入可以同时执行。
情况2:同一段的一次写入和一次读取
同一段的写入和读取可以同时进行。
案例3:同一部分的并发编写
需要锁定段写入,因此对同一段的并发写入将被阻止。
因此,ConcurrentHashMap中的每个段都持有一个锁。在确保线程安全的同时,锁的粒度也降低了,使得并发操作更加高效。
ConcurrentHashMap的Size方法是嵌套循环,一般逻辑如下:
1.遍历所有线段。
2.累加线段的元素数。
3.累计线段的修改次数。
4.确定所有段的总修改时间是否大于上次的总修改时间。大于则表示统计过程有变化,重新统计,尝试次数+1;如果没有。描述不修改,统计完毕。
5.如果尝试次数超过阈值,锁定每个段并重新计数。
6.再次确定所有段的总修改时间是否大于上次的总修改时间。既然已经锁定,那么次数必须等于最后一次。
7.解除锁定,完成统计。
官方源代码如下:
public int size() {
//尝试几次,得到准确的计数。由于以下原因而失败
//表中的连续异步更改,求助于锁定。
最终细分市场& ltk,V >;[]段=这个。分段;
int size
布尔溢出;//如果大小溢出32位,则为true
长和;modCounts之和
long last = 0L//上一个总和
int retries =-1;//第一次迭代不是重试
尝试{
for(;;) {
if(RETRIES++= = RETRIES _ BEfore _ LOCK){
for(int j = 0;j <。片段。长度;++j)
ensureSegment(j)。lock();//强制创建
}
sum = 0L
size = 0;
overflow = false
for(int j = 0;j <。片段。长度;++j) {
细分市场<。k,V >;seg = segmentAt(segments,j);
if (seg!= null) {
sum += seg。modCount
int c = seg。计数;
if(c & lt;0 | |(size+= c)& lt;0)
overflow = true
}
}
if (sum == last)
打破;
last = sum
}
}最后{
if(重试次数>;RETRIES_BEFORE_LOCK) {
for(int j = 0;j <。片段。长度;++j)
segmentAt(segments,j)。unlock();
}
}
返回溢出?整数。MAX_VALUE:大小;
}
为什么要这样设计?这个思想和乐观锁定的悲观思想一模一样。
为了尽量不锁定所有的段,我们首先乐观地假设在大小的过程中不会有任何修改。当尝试一定次数后,变成悲观锁,锁定所有Segment以保证强一致性。
几点注意:
1.这里介绍的ConcurrentHashMap的原理和代码是基于Java1.7的,Java8中有一些不同。
2.对密钥进行两次散列,以实现段的均匀分布。有兴趣的朋友可以研究一下源代码。
—————结束————
1.《concurrenthashmap 漫画:什么是ConcurrentHashMap?》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《concurrenthashmap 漫画:什么是ConcurrentHashMap?》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/junshi/788908.html