并发编程之ConcurrentHashMap源码解析(二)

扩容解析

addCount操作容器大小后扩容

private final void addCount(long x, int check) {
		...省略count加减逻辑,详细见上一章节
        if (check >= 0) {
            Node<K,V>[] tab, nt; int n, sc;
            //sizeCtl 0.75 * 数组长度 且table不为null 且 table长度小于最大值
            while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                    (n = tab.length) < MAXIMUM_CAPACITY) {
                //n为数组长度,以16为例
                //Integer.numberOfLeadingZeros(16) | (1 << (16 - 1))
                //32795
                int rs = resizeStamp(n);
                //sc = sizeCtl 
                //sizeCtl在初始化initTable里赋值为0.75n,16为例等于12
                //肯定大于0
                //其他线程cas竞争sizeCtl失败,此时sc为负数
                if (sc < 0) {
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                            sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                            transferIndex <= 0)
                        break;
                    //其他线程帮助扩容,有一个帮助线程sc + 1
                    //最终判断扩容结束条件为sc = 最初设置值
                    //表示初始扩容线程结束,帮助线程扩容结束
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        transfer(tab, nt);
                }
                //(rs << RESIZE_STAMP_SHIFT) + 2) n为16为例等于-2147483619
                //cas设置sizeCtl为 负很大数
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                        (rs << RESIZE_STAMP_SHIFT) + 2))
                    transfer(tab, null);
                //统计容器大小
                s = sumCount();
            }
        }
    }

主要判断 使用大小 > 0.75 * 数组长度则开始扩容,cas设置sizeCtl为负很大数
竞争sizeCtl失败线程帮助初始扩容线程一起扩容,直到初始扩容线程和帮助线程都扩容结束。

transfer扩容操作

最终扩容操作都在此方法中实现,代码很长,很复杂,慢慢读,ConcurrentHashMap最难的代码。

根据步长计算每个线程同步任务的起始下标和结束下标:
在这里插入图片描述

线程1扩容操作:在这里插入图片描述

如上图,先说明下扩容操作,方便下面代码理解。
假设步长为2,Node数组大小为6,正好3个线程扩容。
1、先新建数组,长度为原先2倍
2、先计算线程扩容的i起始下标和结束下标bound,每个线程对应的开始结束下标不同
3、如图,线程1bound为5,i4,表示线程1先同步Node5,将Node5同步到新的数值中,同步完将老数组的5位置赋值为ForwardingNode节点,表示5节点同步结束。然后i起始下标移动到4位置,同理开始同步4节点。
4、4节点同步结束,会继续请求新的起始下标和结束下标,存在则继续扩容下一个区间,直到所有线程扩容完所有节点
5、所有线程完成后将新的数组复制给table,覆盖老数组

上源码:

 private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        int n = tab.length, stride;
        //计算步长 最小16
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE;
        //新table为空则初始化,大小为原先2倍
        if (nextTab == null) {
            try {
                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
                nextTab = nt;
            } catch (Throwable ex) {
                sizeCtl = Integer.MAX_VALUE;
                return;
            }
            nextTable = nextTab;
            //transferIndex为扩容前数组大小
            transferIndex = n;
        }
        int nextn = nextTab.length;
        //ForwardingNode继承于Node,新建hash为-1的Node
        //Node节点为ForwardingNode表示已扩容完成,废弃
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
        扩容完当前节点,是否需要继续扩容其他节点
        boolean advance = true;
        //当前线程扩容逻辑是否结束
        boolean finishing = false; 
        //自旋
        for (int i = 0, bound = 0;;) {
            Node<K,V> f; int fh;
            //计算线程扩容的i起始下标和结束下标bound
            while (advance) {
                int nextIndex, nextBound;
                //初始时i bound都为0,不会进来
                //i-1 大于等于 bound,表示任在同步区间,不需要计算
                if (--i >= bound || finishing)
                    advance = false;
                //表示扩容完成,transferIndex为0,i为-1
                else if ((nextIndex = transferIndex) <= 0) {
                    i = -1;
                    advance = false;
                }
                //cas设置 transferIndex为 扩容前数组大小-步长
                //所有每个线程过来计算的范围不一样
                //或者只有一个线程,当扩容完后面位置,重新计算起始结束位置,继续循环
                //下一个区间
                else if (U.compareAndSwapInt
                        (this, TRANSFERINDEX, nextIndex,
                                nextBound = (nextIndex > stride ?
                                        nextIndex - stride : 0))) {
                    bound = nextBound;
                    i = nextIndex - 1;
                    advance = false;
                }
            }
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                //扩容完成,nextTab赋值给table
                //sizeCtl
                if (finishing) {
                    nextTable = null;
                    table = nextTab;
                    sizeCtl = (n << 1) - (n >>> 1);
                    return;
                }
                //sizeCtl 初始化扩容时有个初始值,如10
                //其他线程put时发现数组在扩容,会帮助扩容,sizeCtl+1
                //扩容完则sizeCtl-1
                //如果此时sizeCtl等于初始值,表示 帮助线程扩容都完成 且 初始扩容线程扩容完成
                //则表示扩容完成,finishing设为true
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        return;
                    finishing = advance = true;
                    i = n; // recheck before commit
                }
            }
            //取数组[i]的元素,为空直接将其设为fwd,表示此位置废弃
            else if ((f = tabAt(tab, i)) == null)
                advance = casTabAt(tab, i, null, fwd);
            //当前节点是fwd已废弃
            //advance为true,重新进入while循环
            //-i >= bound
            //i减1,下标向前移动,直到移动到bound位置
            else if ((fh = f.hash) == MOVED)
                advance = true; // already processed
            else {
                //对f加锁,防止其他线程操作
                synchronized (f) {
                    //判断位置是否变化,没有变化迁移链表或红黑树
                    if (tabAt(tab, i) == f) {
                        Node<K,V> ln, hn;
                        //hash 大于等于0 表示链表
                        if (fh >= 0) {
                            int runBit = fh & n;
                            Node<K,V> lastRun = f;
                            //循环链表,f为链表首元素,直到f.next没有值为止
                            //计算链表尾元素
                            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);
                            }
                            //cas设置新数组i和i + n位置元素
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            //cas老数组位置设置为fwd,已废弃
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                        //f节点为TreeBin,表示f是个红黑树
                        else if (f instanceof TreeBin) {
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            TreeNode<K,V> lo = null, loTail = null;
                            TreeNode<K,V> hi = null, hiTail = null;
                            int lc = 0, hc = 0;
                            //从红黑树的首元素开始循环
                            for (Node<K,V> e = t.first; e != null; e = e.next) {
                                int h = e.hash;
                                TreeNode<K,V> p = new TreeNode<K,V>
                                        (h, e.key, e.val, null, null);
                                if ((h & n) == 0) {
                                    if ((p.prev = loTail) == null)
                                        lo = p;
                                    else
                                        loTail.next = p;
                                    loTail = p;
                                    ++lc;
                                }
                                else {
                                    if ((p.prev = hiTail) == null)
                                        hi = p;
                                    else
                                        hiTail.next = p;
                                    hiTail = p;
                                    ++hc;
                                }
                            }
                            //ln hn 是否需要转换为链表,否则new TreeBin
                            ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
                                    (hc != 0) ? new TreeBin<K,V>(lo) : t;
                            hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                                    (lc != 0) ? new TreeBin<K,V>(hi) : t;
                            //设置新tab的i i+n 位置为ln hn
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            //将旧tab的f位置设为fwd,废弃
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                    }
                }
            }
        }
    }

针对线程算起始结束位置的算法,提出来具体看下

int i = 0, bound = 0;
boolean advance = true;
while (advance) {
    int nextIndex, nextBound;
    if (--i >= bound || finishing)
        advance = false;
    else if ((nextIndex = transferIndex) <= 0) {
        i = -1;
        advance = false;
    }
    else if (U.compareAndSwapInt
            (this, TRANSFERINDEX, nextIndex,
                    nextBound = (nextIndex > stride ?
                            nextIndex - stride : 0))) {
        bound = nextBound;
        i = nextIndex - 1;
        advance = false;
    }
}

以上扩容图为例,数组长度6,stride步长为2
nextIndex <-- transferIndex 为扩容前数组大小

线程1开始计算:
transferIndex = 6 nextIndex = 6
cas设置transferIndex = 4(nextIndex > stride)
nextBound = 4 bound = 4
i = 5
则线程1计算出的范围为 i5 -bound 4

线程2开始计算:
transferIndex = 4 nextIndex = 4
cas设置transferIndex = 2(nextIndex > stride)
nextBound = 2 bound = 2
i = 3
则线程2计算出的范围为 i3 -bound 2

线程3开始计算:
transferIndex = 2 nextIndex = 2
cas设置transferIndex = 0(nextIndex > stride)
nextBound = 0 bound = 0
i = 1
则线程3计算出的范围为 i1 -bound 0

得出此算法,算出线程扩容的处理区间,处理完成后重新算出下一个区间,多个线程扩容则竞争得出每个扩容区间,然后执行区间内的同步任务。
区间内的同步任务主要步骤:
1、扩容节点为null,直接用fwd节点覆盖老的数组对应下标
2、扩容节点为fwd,表示已扩容完成
3、扩容节点正常,则开始向新数组同步节点,先对节点上锁,防止同步区间其他线程使用,扩容完成用fwd节点覆盖老的数组对应下标
4、向做移动起始坐标i,处理区间前一个节点(右 -> 左)
5、当处理到起始坐标 = 结束坐标,表示区间任务完成
6、重新计算扩容区间

看下扩容结束代码

if (i < 0 || i >= n || i + n >= nextn) {
    int sc;
    if (finishing) {
        nextTable = null;
        table = nextTab;
        sizeCtl = (n << 1) - (n >>> 1);
        return;
    }
    if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
        if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
            return;
        finishing = advance = true;
        i = n; 
    }
}

transferIndex为0时,修改 i 为 -1,表示当前线程的扩容任务完成,无法再申请扩容区间,则sizeCtl - 1后退出扩容操作。
当某个线程任务完成时,sizeCtl等于初始值,表示所有线程扩容全部都完成,因为初始扩容时sizeCtl设置为某个初始值,有一个线程帮助扩容则+1,而一个线程扩容结束则-1,所有sizeCtl等于初始值,表示所有线程扩容全部都完成。finishing设为true,再次循环进入上面if逻辑,nextTab赋值给table。

新数组的下标单独说明下

    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);
   }
     setTabAt(nextTab, i, ln);
     setTabAt(nextTab, i + n, hn);

链表和红黑树扩容,都存在类似逻辑,通过 ph & n 操作将原先数据分为两份。
n为原先数组的长度,一般都为2 n次方,以默认16为例
.,00001011
&00001000
随便一个数,最终结果只于n为1的位数是0和1有关,结果要么0,要么1,完美的将链表或红黑树分为两份。
放入新数组的位置,一份放在原先位置,一份放在原先位置+原数组长度位置在这里插入图片描述

helpTransfer帮助扩容

put操作发现Node节点为Moved,表示当前Node已扩容完成,但整个数组未扩容完成,则去帮助一起扩容,最终调用transfer方法,此方法上面已详述。总结就是去申请扩容区间,同步区间内数据至新数组。

final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
        Node<K,V>[] nextTab; int sc;
        //判断tab不为null且当前节点为ForwardingNode,已扩容完
        //ForwardingNode.nextTab不为空,则帮助下个节点扩容
        if (tab != null && (f instanceof ForwardingNode) &&
                (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
            int rs = resizeStamp(tab.length);
            //循环nextTab未改变且tab未改变,sizeCtl<0
            while (nextTab == nextTable && table == tab &&
                    (sc = sizeCtl) < 0) {
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || transferIndex <= 0)
                    break;
                //设置 sizeCtl+1
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
                    //执行扩容操作
                    transfer(tab, nextTab);
                    break;
                }
            }
            return nextTab;
        }
        return table;
    }

最后

扩容操作终于写完了,真的很绕,希望我画图、分解,能帮助你理解。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值