ConcurrentHashMap源码分析 (3)

这个方法是用来表示已经迁移完毕了,可以退出。

for (int i = 0, bound = 0;;) { ... //如果该槽位没有元素,那么直接把tab的i槽位设置为fwd else if ((f = tabAt(tab, i)) == null) advance = casTabAt(tab, i, null, fwd); //说明这个槽位已经有其他线程迁移过了 else if ((fh = f.hash) == MOVED) advance = true; // already processed //走到这里,说明tab的这个槽位里面有数据,那么我们需要获得槽位的头节点的监视器锁 else { synchronized (f) { if (tabAt(tab, i) == f) { ... } } } ... }

在这个代码块中,i会从最后一个元素一个个往前移动,然后根据i这个index来判断tab里面槽位的情况。

下面的代码我们来分析监视器锁里面的内容:

synchronized (f) { if (tabAt(tab, i) == f) { //fh是当前节点的hash值 if (fh >= 0) { int runBit = fh & n; //lastRun设置为头节点 Node<K,V> lastRun = f; // 需要将链表一分为二, // 找到原链表中的 lastRun,然后 lastRun 及其之后的节点是一起进行迁移的 // lastRun 之前的节点需要进行克隆,然后分到两个链表中 for (Node<K,V> p = f.next; p != null; p = p.next) { int b = p.hash & n; if (b != runBit) { runBit = b; lastRun = p; } } if (runBit == 0) { ln = lastRun; hn = null; } else { hn = lastRun; ln = null; } for (Node<K,V> p = f; p != lastRun; p = p.next) { int ph = p.hash; K pk = p.key; V pv = p.val; if ((ph & n) == 0) ln = new Node<K,V>(ph, pk, pv, ln); else hn = new Node<K,V>(ph, pk, pv, hn); } //其中的一个链表放在新数组的位置 i setTabAt(nextTab, i, ln); //另一个链表放在新数组的位置 i+n setTabAt(nextTab, i + n, hn); //将原数组该位置处设置为 fwd,代表该位置已经处理完毕 //其他线程一旦看到该位置的 hash 值为 MOVED,就不会进行迁移了 setTabAt(tab, i, fwd); //advance 设置为 true,代表该位置已经迁移完毕 advance = true; } //下面红黑树的迁移和上面差不多 else if (f instanceof TreeBin) { .... } } }

这个方法主要是将头节点里面的链表拆分成两个链表,然后设置到新的数组中去,再给老的数组设置为fwd,表示这个节点已经迁移过了。

到这里transfer方法已经分析完毕了。
这里我再举个例子,让大家根据透彻的明白多线程之间是怎么进行迁移工作的。

我们假设stride还是默认的16,第一次进来nextTab为null,但是tab的长度为32。 一开始的赋值: 1. n会设置成32,并且n只会赋值一次,代表被迁移的数组长度 2. nextTab会被设置成一个大小为64的数组,并塞入到新的ForwardingNode对象中去。 3. transferIndex会被赋值为32 进入循环: 初始化i为0,bound为0; 第一次循环: 1. 由于advance初始化为true,所以会进入到while循环中,循环出来后,transferIndex会被设置成16,bound被设置成16,i设置成31。这里你可能会问 2. 将原来tab[i]的元素迁移到新的数组中去,并将tab[i]设置为fwd,将advance设置成为true 第二次循环: 1. --i,变为30,--i >= bound成立,并将advance设置成false 2. 将原来tab[i]的元素迁移到新的数组中去,并将tab[i]设置为fwd,将advance设置成为true 。。。 第十六次循环: 1. --i,变为15,将transferIndex设置为0,bound也设置为0,i设置为15 2. 将原来tab[i]的元素迁移到新的数组中去,并将tab[i]设置为fwd,将advance设置成为true 第三十二次循环: 1. 这个时候--i等于-1,并且(nextIndex = transferIndex) <= 0成立,那么会将i设置为-1,advance设置为false 2. 会把SIZECTL用CAS设置为原来的值加1,然后设置finishing为true 第三十三次循环: 1. 由于finishing为true,那么nextTable设置为null,table设置为新的数组值,sizeCtl设置为旧tab的长度的1.5倍

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpjxjs.html