mvp是什么意思,iphone-韩国版宣传栏,韩流流行世界,韩国明星养成指南

自己是作业7年的老程序员,在头条共享我对Java运用和源码、各种结构运用和源码的知道和了解,假如对您有所帮忙,请持续重视。

声明:一切的文章都是自己作业之余一个字一个字码上去的,期望对学习Java的同学有所帮忙,假如有了解不到位的当地,欢迎沟通。

上一篇文章我对ConcurrentHashMap的十分重要的put办法做了一个全面的解析,可是遗留了两个也十分重要的知识点:扩容和线程安全,接下来两篇文章就环绕这两个内容打开。此篇内容是我经过几天的闲暇时刻写出来的,由于篇幅过长,请耐性看完,信任对你会有一切帮忙,假如有了解不到位的当地,欢迎沟通。首要贴出扩容的流程图

ConcurrentHashMap扩容的流程图

本篇文章的首要内容如下:

1:回想一下HashMap是怎样扩容的
2:ConcurrentHashMap什么时分会进行扩容
3:ConcurrentHashMap的扩容详解

一、回想一下HashMap是怎样扩容的

在HashMap中数组的初始化和扩容用杨建邦微博的是同一个办法,相对ConcurrentHashMap仍是比较简略了解的,下面我就对HashMap的扩容总结一下以及什么时分才进行扩容,十分具体的HashMap扩容机制请看HashMap扩容机制

1:HashMap的扩容总结

1:首要判别数组是否被初始化,假如没有被初始化,则进行初始化作业,判别是否被初始化的条件:table==numvp是什么意思,iphone-韩国版宣传栏,韩流盛行国际,韩国明星养成攻略ll || table.length==0
2:假如是初始化,依据不同的结构函数进行初始化:
2.1:假如是无参结构函数,则默许数组的长度为16,扩容阀门为16*0.75=12
2.2:假如是有参数结构函数,则数组的长度便是thresmvp是什么意思,iphone-韩国版宣传栏,韩流盛行国际,韩国明星养成攻略hold.
3:假如现已初始化了,判别条件:int oldCap = (oldTab == null) ? 0 : oldTab.length>0
扩容规矩:数组的长度扩展为本来的2倍,扩容阀门threshold扩展为本来的2倍。
4:搬迁老数据:
4.1:假如老的下标只要一个元素,则直接经过(newCap-1)&hash核算出新的下标
4.2:假如老的下标是一个红黑树,则使用红黑树的搬迁规矩
4.3:假如老的下标是一个链表,则使用链表的搬迁规矩

HashMap搬迁红黑树和链表时做了一个优化,由于HashMap数组的长度都是2的n次方幂,所以数组长度扩展2倍后,用二进制标明:前面多出了一个高位1,所以元素放到本来的方位,仍是本来方位+oldCap,就要看这个元素和多出那一个高位1是怎样的了,假如元素的那一位也是1,则是本来的方位+oldCap,假如是0,则是本来的方位,具体的请看HashMap扩容机制

2:HashMap什么是否才会进行扩容

由于初始化和扩容是同一个办法,所以首要有两个部分用到
1:当榜首次调用put时进行初始化作业
2:当参加新的元素时,假如调集元素大于了阀门,就会调用扩容办法

HashMap的调用内容如图所示:

二、ConcurrentHashMap什么时分会进行扩容

经过读ConcurrentHashMap的源码知道,扩容的首要作业就在transfer办法中,咱们稍后会对这个办法进行具体的解说,接下来咱们看看ConcurrentHashMap中哪些当地调用了transfer进行扩容的。如下图所示

ConcurrentHashMap调用transfer的当地

接下来咱们一个一个的去剖析。

1:addCount办法中:

while (s >= (long)(s范泉智c = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
//进入while循环:阐明当时的元素数量大于了sizeCtrl,就需求扩容了。
int rs = resizeStamp(n);
if (sc < 0) {
//走到这一步:说飘移赛车明sizeCtrl<0,阐明正在扩容,所以需求当时线程帮忙扩容,需求不需求帮忙需求进行判别
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
//走到这一步:阐明不需求帮忙了。
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
//走到这一步:阐明需求进行帮忙扩容,上面的CAS操作便是将扩容线程+1.
transfer(tab, nt);
}
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
//走到这一步:阐明当时线程是榜首个扩容的线程
transfer(tab, null);
}

addCount办法会在putVal()办法最终调用,有首要有两个效果:

1:高并发的对元素的总数进行操作,也便是对C完美国际ounterCell和baseCount的操作。
1.1:假如没有并发,每添加一个元素就使用CAS机制添加baseCount.
1.2:假如有并发,则经过操作CounterCell数组。
2:判别是否需求扩容。
2.1:假如sizeCtrl<0:阐明其他线程正在扩容,当时线程就需求判别是否能够辅佐扩容了。
2.2:假如sizeCtrl>0:阐明还没有其他线程进行扩容,当时线程是榜首个扩容的线程。章一城微博

两个调用的当地仍是有差异的,假如是辅佐其他线爱笑的眼睛程进行扩容则trans西贵银fer的最终一个solution参数不为空,假如当时线程是榜首个扩容的线程,则最终一个参数为空。addCount的流程图如下:

addCount办法的流程图

2:helpTransfer办法中

我在剖析ConcurrentHashMap的put办法中讲到过这个helpTransfer办法,首要是帮忙其他线程进行扩容,具体的剖析请看哪一篇文章,helpTransfer的流程图如下:

helpTransfer办法的流程图

3:tryPresize办法中

从字面上了解这个办法的意义便是测验扩容,首要有两个当地调用了这个办法,如图

调用tryresize办法的当地

1:putAll()办法:把给定的Map调集参加到ConcurrentHashMap中
2:treeifyBin()办法:这个办法是在链表转换成红黑树的时分,所以当链表长度大于8时,链表不一定就转换成红黑树,假如此刻底层数组的长度小于64,则进行扩容,否则将链表转换成红黑树

tryPresize的源代码如下:

private final void tryPresize(int size) {
int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
tableSizeFor(size + (size >>> 1) + 1);
int sc;
while ((sc = sizeCtl) >= 0) {
Node[] tab = table; int n;
if (tab == null || (n = tab.length) == 0) {
//走到这一步$1:阐明数组还未初始化,则进行初始化作业,和initTab的办法流程相同。
n = (sc > c) ? sc : c;
if (U.compareAndSwapI后舍男生不得不爱nt(this, SIZECTL, sc, -1)) {
try {
if (table == tab) {
@SuppressWarnings("unchecked")
Node[] nt = (Node[])new Node
table = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
}
}
else if (c <= sc || n >= MAXIMUM_CAPACITY)
//走到这一步$2:阐明不需求进行扩容或许容量现已到达最大
break;
else if (tab == table) {
//走到这一步$3:测验进行扩容或许辅佐其他线程进行扩容了
int rs = resizeStamp(n);
if (sc < 0) {
Node[] nt;
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
1:(sc >>> RESIZE_STAMP_开花梨SHIFT) != rs:标明现已扩容完毕
2:sc == rs + 1 || sc == rs + MAX_RESIZERS:这两个判别是扩容线程现已到达最大,可是在ConcurrentHashMap中这两个永远都是false
3: (nt = nextTable) == null || transferIndex <= 0:标明扩容完毕
//走到这一步:阐明不需求进行帮忙扩容
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
//经过CAmvp是什么意思,iphone-韩国版宣传栏,韩流盛行国际,韩国明星养成攻略S将扩容线程+1,成功后则帮忙进行扩容
transfer(tab, nt);
}
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
//当时线程是榜首个扩容的线程。
transfer(tab, null);
}
}
}

tryPresize()办法首要有3个分支流程:

$1:判别底层数组是否被初始化,假如没有初始化则进行初始化,这个在putAll时会呈现,而treeifyBin不会进入这个条件中
$2:咱们讲过sizeCtrl,如塞巴斯蒂安果元素的数量大于等于sizeCtrl时将会进行扩容:c<=sc,假如不符合这个条件,则无需扩容,假如sizeCtrl到达了最大值,则也不能扩容了。
$3:这一步便是测验进行扩容了,首要要判别当时线程是否能够进行扩容或许帮忙扩容
榜首个条件:(sc >>> RESIZE_STAMP_SHIFT) != rs:标明现已扩容完毕了。
第二个条件和第三个条件:sc == rs + 1 || sc == rs + MAX_RESIZERS:这两个条件标明扩容线程现已到达了最大值,当时线程不能进行扩容了,可是在ConcurrentHashMap中这两个条件永远都是false
第四个条件和第五个条件:标明扩容完毕了
假如这5个条件恣意一个为true,则当时线程不需求扩容或许帮忙扩容了,直接跳出循环。
假如这5个条件都是false,则需求判别是否正在扩容,假如正在扩容,则帮忙其他线程进行扩容,假如没有进行扩容,则当时线程是榜首个扩容线程。

tryPresize的流程图如下:

tryPresize办法的流程图

三、ConcurrentHashMap的扩容详解

上面咱们了解了什么时分会调用transfer办法,也了解了HashMap的扩容十分的简略,它不必考虑多线程的状况,可是ConcurrentHashMap就不相同了,它需求考虑多线程的状况,而且为了功能的原因,Doug Lea大神规划了其他线程帮忙扩容的机制,所以它的扩容变得比较复杂了,接下来咱们具体剖析它的扩容机制是怎样规划的。

剖析扩容细节之前,我总结了扩容的两个关键:

榜首个关键:底层数组苹果官网香港的扩容:一般会扩展到本来的两倍,而且只允许一个线程进行,不允许多线程并发扩展数组的长度。
第二个关键:数据的搬迁,便是把已存在的元素搬迁到新的皇家礼炮数组中。这个阶段能够多线程并发辅佐扩容。

那在扩容时,tre200ansfer办法怎样体现出这两个关键呢?如图:

扩容两个关键的源码截图

上面咱们剖析了什么时分调用transfer办法,只要当时线程为榜首个扩容线程时,transfer的第二个参数才为null,所以只能是榜首个扩容线程才干扩展数组的容量。下面的for循环便是对老数据的搬迁作业了。

咱们结合上面两个关键开端对transfer办法进行具体的剖析,仍是老准则,贴出transfer的源代码。

榜首个关键:扩展数组的长度

private final void tran余sfer(Node[] tab, Node[] nextTab) {
int n = tab.length, stride;
//stride:在数据搬迁时,每一个线程要担任搬迁老数组table的多少个桶(数组下标)
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // subdivide range
if (nextTab == null) { // initiating
//走到这一步:只要榜首个扩容线程才干走到这一步,创立一个新的Node数组,长度是本来数组的2倍。
try {
@SuppressWarnings("unchecked")
Node[] nt = (Node[])new Node<< 1];
nextTab = nt;
} catch (Throwable ex) { // try to cope with OOME
sizeCtl = Integer.MAX_VALUE;
return;
}
//将创立的新的数组赋值给nextTable.将老数组的长度赋值给transferIndex
nextTable = nextTab;
transferIndex = n;
}
//.....搬迁老的数据,等会剖析
}

上面的代码要了解如下内容:

1:n:老数组的长度
2:stride:在数据搬迁时,每一个线程担任搬迁老数组的多少个桶(也便是数组的下标),最少为16个,所以从这能够看出,假如使用默许的结构函数创立Map,榜首次扩容时只能有一个线程进行扩容,由于n=16.
3:nextTable:全局变量,private transient volatile Node[] nextTable:只要在扩容时才不为空。
4:新数组的长度变成老数组的2倍。
5:transferIndex:扩容线程要搬迁下标的符号,从老数组的结尾到首部(从右到左)进行数据搬迁。例如:
榜首个扩容线程要搬迁的桶:[transferIndex-stride,transferIndex-1]
第二个扩容线程要搬迁的桶:[transfeflexiblerIndex-2*stride,transferIndex-stride-1]
最终一个扩容线程要搬迁的桶:[0,stride-1]

上面的把数组扩展到本来的2倍没有什么难度了,一些变量我也做了阐明,接下来咱们看看怎样把老数组中的数据搬迁到新的数组中去。

榜首部分代码:

int nextn = nextmvp是什么意思,iphone-韩国版宣传栏,韩流盛行国际,韩国明星养成攻略Tab.length;
//扩容时的节点封装,当一个下标的数据搬迁完结后,会被标成ForwardingNode
ForwardingNode fwd = new ForwardingNode(nextTab)北漂明星梦之血泪史;
//标明老数组的一个下标的数据是否搬迁完结
boolean advance = true;
//最终一个帮忙扩容线程会把此值设置成true,标明数据搬迁完结。
boolean finishing = false;
//i:标明搬迁老数组中的下标
//mvp是什么意思,iphone-韩国版宣传栏,韩流盛行国际,韩国明星养成攻略bound:标明一个扩容线程搬迁的最终一个桶(最终一个下标),由于搬迁数据是从右到左的。
//i和bound便是代表的[bound=transferIndex-stride,i=transferIndex-1]。
//for循环:搬迁[bound,i]之间的数据。
for (int i = 0, bound = 0;;) {
Node f; int fh;
//while循环:核算出当时线程要搬迁数据的下标规模,例如[bound=0,i=15]
while (advance) {
int nextIndex, nextBound;
if (--i >= bound || finishing)
//走到这一步$1:阐明当时线程现已核算出了要担任搬迁的数据规模
advance = false;
else if ((nextIndex = transferIndex) <= 0) {
//走到这一步$2:阐明数据现已搬迁完结了,无需在搬迁了。
i = -1;
advance = false;
}
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
//走到这一步$3:阐明核算出当时线程要核算出担任搬迁数据的规模
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}

上面的代码总结如下:

1:while循环的意思:核算出当时线程要搬迁数据的数组下标规模[bound,i]
2:for循环的意思:便是从右到左开端搬迁数据,便是从i开端搬迁数据,直到bound完毕,当时线程搬迁数据完毕。

1

为了更好的了解这个while循环,举例如下:

一个线程进入了while循环,此刻i=0,bound=0.

1:首要判别if句子,不符合,持续判别
2:进入else if句子,nextIndex=transferIndex=16>0,不符合,持续判别
3:进入else if句子。经过CAS判别,成功后
nextIndex=16.
nextBound=0:经过核算很简略获取。所以bound=0.
i=nextIndex-1=15.
所以核算出当时线程搬迁数组的下标[bound,i]=[0,15],可是不是从bmvp是什么意思,iphone-韩国版宣传栏,韩流盛行国际,韩国明星养成攻略ound到i搬迁的,而是从i到bound倒着搬迁的。

while循环核算i和bound

上面经过while循环现已核算出了当时线程要搬迁数据数组下标的规模,那么接下来就要进行真实的搬迁数据了,接下来咱们看看怎样搬迁的。咱们首要看一些简化的代码

for (int i = 0, bound = 0;;) {
while (advance){
//while上面现已剖析,这儿代码省掉
}
if (i < 0 || i >= n || i + n >= nextn) {
//$1:现已搬迁完结,进行一些扫尾作业
}
else if ((f = tabAt(tab, i)) == null){
//$2:此数组下标没有数据
}
else if ((fh = f.hash) == MOVED)
//$3:此数组下标现已搬迁过了
else {
//$4:开端搬迁,要么是链表、要么是红黑树
}
}

经过上面简化的代码能够看出,经过while循环获取到搬迁数组的下标规模后,经过for循环迭代核算的下标规模,然后经过条件句子进入不同的逻辑,下面咱们具体剖析这些条件句子。

1:$1:现已搬迁完结,需潘爱国要进行扫尾作业

if (i < 0 || i >= n || i + n >= nextn) {
int sc;
if (finishing) {
//进入这一步:将新的数组赋高铁一等座和二等座的差异值给全局变量table,sizeCtrl变成0.75table.length.搬迁完结,退出无限循环。
nextTable = null;
table = nextTab;
sizeCtl = (n << 1) - (n >>> 1);
return;
}
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
//进入这一步:经过CAS将搬迁线程-1成功后进入。
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
//进入这一步:阐明一切的搬迁数据的线程现已没有了,退出循环
return;
finishing = advance = true;
i = n; // recheck before commit
}
}

2:$2:此数组下标没有数据,将此下标符号为ForwardingNode节点

else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null,mvp是什么意思,iphone-韩国版宣传栏,韩流盛行国际,韩国明星养成攻略 fwd);

3:$3:此下标现已是ForwardingNode节点,阐明此下标现已搬迁过

else if ((fh = f.hash) == MOVED)
advance = true; // already processed

4:$4:开端搬迁数据,要么搬迁的是链表,要么搬迁的是红黑树

else {
//搬迁一个下标数据时,要加锁。
synchronized (f) {
//此数组和HashMap的搬迁相似,这儿就不在把代码贴出来了。
}
}

上面搬迁链表和红黑树时,和HashMap的机制相同,假如不熟悉,请看HashMap的文章。这儿我就不在重复这些内容了。上面咱们就完好剖析了ConcurrentHashMap的扩容机制,为了让流程愈加的明晰,我接下来用一个流程图来剖析。

ConcurrentHashMap扩容的流程图

 关键词: