ConcurrentHashMap源码分析 (2)

下面我们在看看treeifyBin方法

private final void treeifyBin(Node<K,V>[] tab, int index) { Node<K,V> b; int n, sc; if (tab != null) { //如果数据的长度小于64,那么调用tryPresize进行扩容 if ((n = tab.length) < MIN_TREEIFY_CAPACITY) tryPresize(n << 1); //如果这个槽位里面的元素是链表 else if ((b = tabAt(tab, index)) != null && b.hash >= 0) { //给链表头加上锁 synchronized (b) { if (tabAt(tab, index) == b) { TreeNode<K,V> hd = null, tl = null; //遍历链表,然后初始化红黑树对象 for (Node<K,V> e = b; e != null; e = e.next) { TreeNode<K,V> p = new TreeNode<K,V>(e.hash, e.key, e.val, null, null); if ((p.prev = tl) == null) hd = p; else tl.next = p; tl = p; } //给tab槽位为index的元素设置新的对象 setTabAt(tab, index, new TreeBin<K,V>(hd)); } } } } }

treeifyBin这个方法里面并不是只是将链表转化为红黑树,而是当tab的长度大于64的时候才会将链表转成红黑树,否则的话,会调用tryPresize方法。

然后我们进入到tryPresize方法里面看看,tryPresize传入的参数是当前tab数组长度的两倍。

private final void tryPresize(int size) { //原本传进来的size已经是两倍了,这里会再往上取最近的 2 的 n 次方 int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(size + (size >>> 1) + 1); int sc; while ((sc = sizeCtl) >= 0) { Node<K,V>[] tab = table; int n; // 这个 if 分支和之前说的初始化数组的代码基本上是一样的,在这里,我们可以不用管这块代码 if (tab == null || (n = tab.length) == 0) { n = (sc > c) ? sc : c; if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { if (table == tab) { @SuppressWarnings("unchecked") Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; table = nt; sc = n - (n >>> 2); } } finally { sizeCtl = sc; } } } else if (c <= sc || n >= MAXIMUM_CAPACITY) break; else if (tab == table) { int rs = resizeStamp(n); //一开始进来的时候sc是大于0的 if (sc < 0) { Node<K,V>[] nt; 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)) transfer(tab, nt); } //将SIZECTL设置为一个很大的复数 else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) transfer(tab, null); } } }

这个方法里面,会对tab数据进行校验,如果没有初始化的话会重新进行初始化大小,如果是第一次进来的话会将SIZECTL设置成一个很大的复数,然后调用transfer方法,传如当前的tab数据和null。

接着我们来看transfer方法,这个方法比较长,主要的扩容和转移节点都在这个方法里面实现,我们将这个长方法分成代码块,一步步分析:

private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) { //如果当前tab数组长度为16 int n = tab.length, stride; //那么(n >>> 3) / NCPU = 0 小于MIN_TRANSFER_STRIDE if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) //将stride设置为 16 stride = MIN_TRANSFER_STRIDE; // subdivide range if (nextTab == null) { // initiating try { @SuppressWarnings("unchecked") Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; //如果n是16,那么nextTab就是一个容量为32的空数组 nextTab = nt; } catch (Throwable ex) { // try to cope with OOME sizeCtl = Integer.MAX_VALUE; return; } nextTable = nextTab; //将transferIndex赋值为16 transferIndex = n; } ... }

这个代码块主要是做nextTable、transferIndex 、stride的赋值操作。

... //初始化nextn为32 int nextn = nextTab.length; //新建一个ForwardingNode对象,里面放入长度为32的nextTab数组 ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab); boolean advance = true; boolean finishing = false; //初始化bound为0 for (int i = 0, bound = 0;;) { ... }

下面的代码会全部包裹在这个for循环里面,所以我们来分析一下这个for循环里面的代码

for (int i = 0, bound = 0;;) { Node<K,V> f; int fh; while (advance) { int nextIndex, nextBound; if (--i >= bound || finishing) advance = false; //将nextIndex设置为transferIndex,一开始16 else if ((nextIndex = transferIndex) <= 0) { i = -1; advance = false; } //一开始的时候nextIndex是和stride相同,那么nextBound为0,TRANSFERINDEX也为0 else if (U.compareAndSwapInt (this, TRANSFERINDEX, nextIndex, nextBound = (nextIndex > stride ? nextIndex - stride : 0))) { //这里bound也直接为0 bound = nextBound; //i = 15 i = nextIndex - 1; advance = false; } } ... }

这个方法是为了设置transferIndex这个属性,transferIndex一开始是原tab数组的长度,每次会向前移动stride大小的值,如果transferIndex减到了0或小于0,那么就设置I等于-1,i在下面的代码会说到。

for (int i = 0, bound = 0;;) { ... //在上面一段代码块中,如果transferIndex已经小于等于0了,就会把i设置为-1 if (i < 0 || i >= n || i + n >= nextn) { int sc; //表示迁移已经完成 if (finishing) { //将nextTable置空,表示不需要迁移了 nextTable = null; //将table设置为新的数组 table = nextTab; //sizeCtl设置为n的 1.5倍 sizeCtl = (n << 1) - (n >>> 1); return; } if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) { if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT) return; // 到这里,说明 (sc - 2) == resizeStamp(n) << RESIZE_STAMP_SHIFT, // 也就是说,所有的迁移任务都做完了,也就会进入到上面的 if(finishing){} 分支了 finishing = advance = true; i = n; // recheck before commit } } ... }

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

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