ConcurrentHashMap第四讲:transfer方法

在这里插入图片描述
transfer方法是是在addCount 方法中被调用的。

  • 情况一:当该线程发现其它线程在扩容时,调用transfer 协助扩容
  • 情况二:当前线程触发扩容条件,调用transfer进行扩容

扩容的新数组是旧数组的两倍,旧数组的fwd节点 引用连接着新数组
当迁移该桶位时,会用synchronize 锁住桶位的头节点

ConcurrentHashMap的数组中有三种节点:

  1. 普通节点
  2. TreeBin节点
  3. FWD节点
  • 普通节点:普通链表节点,单向链表
  • TreeBin节点:里面维护两种形式的节点,一种是TreeNode树节点(红黑树),一种是TreeNode链表节点(双向链表)
  • FWD节点:是链接新的数组的引用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在数组扩容后,进行迁移链表节点时就是按上面这个图进行的。

注:Treebin 头节点下面有红黑树 也有 链表

transfer源码

     * 当触发扩容时调用 transfer时,nextTbable 为空
     * 当不触发扩容时,协助扩容时,调用 transfer,nextTbable 不为空
     */
    private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        // n 表示扩容之前table的数组长度
        // stride 分配给线程任务的步长    比如128长度数组 要迁移到 256 长度的数组中。需要步长
        int n = tab.length, stride;
        //  方便讲解 stride 固定为16
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; // subdivide range

        //  条件成立:nextTab==null 为true,表示触发扩容
        //  条件不成立:nextTab!=null,表示协助扩容
        if (nextTab == null) {            // initiating
            try {
                // 创建了一个比table 大一倍的数组
                @SuppressWarnings("unchecked")
                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
                nextTab = nt;
            } catch (Throwable ex) {      // try to cope with OOME
                sizeCtl = Integer.MAX_VALUE;
                return;
            }
            // 赋值给对象属性,方便协助扩容线程  拿到新表
            nextTable = nextTab;
            // 记录迁移数据整体位置的一个标记,index 计数从 1 开始(从16 到 1)
            transferIndex = n;
        }
        // 表示新数组的长度
        int nextn = nextTab.length;
        //fwd节点,当前桶位数据处理完毕后,将此桶位设置为fwd节点,其它写线程 或读线程看到后,会有不同逻辑
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
        // 推进标记
        boolean advance = true;
        // 完成标记
        boolean finishing = false; // to ensure sweep before committing nextTab

        // 自旋
        // i 表示当前线程任务 执行到的桶位
        // bound 表示给当前线程任务的下界限制 比如说:迁移fwd是从高位到地位,15 -> 10 bound表示这个范围,低于10 就越界了
        for (int i = 0, bound = 0;;) {
            // f 桶位的头节点
            // fh 头节点的hash
            Node<K,V> f; int fh;

            /**
             * 1.给当前线程分配线程区间
             * 2.维护当前线程任务进度(i 表示当前处理的桶位)
             * 3.维护map 对象全局范围内的进度  (advance = true 代表继续推进)
             */
            while (advance) {
                // 分配任务区间有关系
                //nextIndex 分配任务开始下标
                //nextBound 分配任务结束下标
                int nextIndex, nextBound;

                //CASE1:
                // --i >= bound 成立表示:如果当前线程的任务还没有完成
                // 还有相应的区间的桶位要处理
                // 不成立:表示当前线程任务已经完成 或 未分配
                if (--i >= bound || finishing)
                    advance = false;
                //CASE2:
                // 前置条件:当前线程任务已完成 或 未分配
                // 条件成立:表示全局范围的桶位都分配完毕了,没有区间可分配了,设置当前线程的i变量为 -1 跳出循环后,执行退出迁移任务相关的程序
                // 条件不成立:表示对象全局范围内的桶位尚未分配完毕,还有区间可分配
                else if ((nextIndex = transferIndex) <= 0) {
                    i = -1;
                    advance = false;
                }
                //CASE3:
                // 前置条件:1.当前线程需要分配任务区间 2.全局范围内还有桶位尚未迁移
                // 条件成立:说明给当前线程分配任务成功
                // 条件失败:说明分配给当前线程失败,应该是和其它线程发生了竞争吧
                // TRANSFERINDEX 地址上的值如果是nextIndex,就改变为(nextIndex - stride == 0)
                else if (U.compareAndSwapInt
                         (this, TRANSFERINDEX, nextIndex,
                          nextBound = (nextIndex > stride ?
                                       nextIndex - stride : 0))) {
                    //任务结束的下标
                    bound = nextBound;
                    // transferIndex 是从 1开始的,nextIndex是从 0 开始的,所以需要 -1
                    i = nextIndex - 1;
                    advance = false;
                }
            }

            /** 处理线程任务完成后,线程退出transfer 方法的逻辑 */
            // 条件一:i < 0
            // 成立:表示当前线程未分配到任务
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                // 保存sizeCtl 的变量
                if (finishing) {
                    nextTable = null;
                    table = nextTab;
                    // sizeCtl = 2n - 1/2n = 3/2n = 0.75 * 2n (下次扩容的阈值)
                    sizeCtl = (n << 1) - (n >>> 1);
                    return;
                }
                // sizeCtl 后16位是线程数量,退出前需要将数量 -1
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                    //1000 0000 0001 1011 0000 0000 0000 0000
                    // (sc - 2) == resizeStamp(n) << RESIZE_STAMP_SHIFT:说明当前线程是最后一个退出transfer的线程
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        // 不是最后一个线程,直接退出
                        return;
                    finishing = advance = true;
                    i = n; // recheck before commit
                }
            }

            /**
             * 线程处理一个桶位数据的迁移工作,处理完毕后设置advance 为true 表示继续推进,然后就会回到for
             */
            // case2到case4的 前置条件:当前任务尚未处理完,正在进行中
            //CASE2:
            // 如果当前桶位的头节点是null,只需要让它指向fwd节点
            else if ((f = tabAt(tab, i)) == null)
                // 没有数据 不用加锁
                advance = casTabAt(tab, i, null, fwd);
            //CASE3:
            // 条件成立:说明当前桶位已经迁移过了,当前线程不用再成立了,直接再次更新当前线程任务索引,再处理下一个桶位 或者其他操作
            else if ((fh = f.hash) == MOVED)
                advance = true; // already processed
            //CASE4:
            // 前置条件:当前桶位有数据,而且node节点 不是fwd节点, 说明这些节点需要迁移
            else {
                // 加锁 当前桶位的头节点
                synchronized (f) {
                    // 再次判断锁的头节点对不对,上面的条件与该else之间,f可能被更改,锁的不对需要自旋
                    if (tabAt(tab, i) == f) {
                        // ln表示低位链表的引用
                        // hn表示高位链表的引用
                        Node<K,V> ln, hn;

                        // 条件成立:当前桶位是链表桶位
                        if (fh >= 0) {

                            // lastRun
                            // 可以获取出 当前链表 末尾连续
                            int runBit = fh & n;
                            Node<K,V> lastRun = f;
                            for (Node<K,V> p = f.next; p != null; p = p.next) {
                                int b = p.hash & n;
                                if (b != runBit) {
                                    runBit = b;
                                    lastRun = p;
                                }
                            }

                            // 说明链表后面连续的是 0xxxx ,b是0,ln低位链表指向lastRun,后面不用再创建了
                            if (runBit == 0) {
                                ln = lastRun;
                                hn = null;
                            }
                            // 说明链表后面连续的是 1xxxx ,b是1,hn高位链表指向lastRun,后面不用再创建了
                            else {
                                hn = lastRun;
                                ln = null;
                            }

                            //迭代链表:跳出条件:当前循环节点 不等于lastRun
                            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)
                                    // 创建node(ph,pk,pv) 插入 到低位链表的头部
                                    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);
                            // 把原来表的 i桶位 设置为fwd节点。fwd指向新表
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }

                        // 条件成立:当前桶位是红黑树代理节点 TreeBin
                        else if (f instanceof TreeBin) {
                            // 转换头节点为 treeBin引用 t
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            // 低位双向链表 lo 指向 低位链表的头,loTail指向 低位链表的尾巴
                            TreeNode<K,V> lo = null, loTail = null;
                            // 高位双向链表 hi 指向 高位链表的头,hiTail指向 高位链表的尾巴
                            TreeNode<K,V> hi = null, hiTail = null;

                            // lc 表示低位链表元素数量
                            // hc 表示高位链表元素数量
                            int lc = 0, hc = 0;

                            // 迭代TreeBin 中的双向链表,从头节点到尾节点
                            for (Node<K,V> e = t.first; e != null; e = e.next) {

                                //h 代表循环当前节点的hash值
                                int h = e.hash;
                                // 使用当前节点 构建出来的新的TreeNode
                                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;
                                    // 数量+1
                                    ++lc;
                                }

                                // 当前节点 应该加入到高位链中
                                // 高位和低位一样
                                else {
                                    if ((p.prev = hiTail) == null)
                                        hi = p;
                                    else
                                        hiTail.next = p;
                                    hiTail = p;
                                    ++hc;
                                }
                            }
                            // 低位链表数量小于阈值6,把低位链双向链表TreeNode 转换为单向链表node
                            ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
                                // hc != 0 说明高位也有数据,有两个链表,需要再创建TreeBin放低位链表
                                // 如果高位没有数据,也就是说没有分成两条链表,只有一条,就直接把原来的 t放进去就行了,不用再创建了
                                (hc != 0) ? new TreeBin<K,V>(lo) : t;
                            //分析和低位一样
                            hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                                (lc != 0) ? new TreeBin<K,V>(hi) : t;
                            // 低位放低位链表
                            setTabAt(nextTab, i, ln);
                            // 高位放高位链表
                            setTabAt(nextTab, i + n, hn);
                            // 原来的链表放fwd节点,指向新表
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                    }
                }
            }
        }
    }

helpTransfer源码

  final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
        // nextTab 引用的是 fwd.nextTable == map.nextTable 理论上是这样
        //sc 保存map.sizeCtl
        Node<K,V>[] nextTab; int sc;

        // 条件一:tab != null 恒成立
        // 条件二:f instanceof ForwardingNode 恒成立
        // 条件三:nextTab = ((ForwardingNode<K,V>)f).nextTable) != null 恒成立
        if (tab != null && (f instanceof ForwardingNode) &&
            (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {

            // 拿到当前表的 扩容戳 假设 16 -> 32 扩容戳:1000 0000 0001 1011
            int rs = resizeStamp(tab.length);

            // 条件一:nextTab == nextTable
            // 成立:表示当前扩容正在进行中
            // 不成立: 1.nextTbale 被设置为null,扩容完毕后,会被设为null
            //          2.再次触发扩容。。拿到的nextTab已经过期
            // 条件二:table == tab
            //      成立:什么扩容正在进行中,还未完成
            //      不成立:什么扩容已经结束了,扩容结束之后,最后退出的线程会设置 nextTable 为 table
            // 条件三:(sc = sizeCtl) < 0
            //      成立:说明扩容正在进行中
            //      不成立:说明sizeCtl当前是一个大于0的数,此时代表下次扩容的阈值,当前扩容已经结束
            while (nextTab == nextTable && table == tab &&
                   (sc = sizeCtl) < 0) {

                // 条件一:(sc >>> RESIZE_STAMP_SHIFT) != rs
                //          true-> 说明当前线程获取到的扩容唯一标识戳 非 本次扩容
                //          false-> 说明当前线程获取到的扩容唯一标识戳 是 本次扩容
                // 条件二:jdk1.8中有bug_jira:其实想表达的是:sc == (rs << 16) + 1
                //          true-> 表示扩容完毕,当前线程不需要再参与进来了
                //          false-> 扩容还在进行时,当前线程可以参与进来
                // 条件三:jdk1.8中有bug_jira:应该是:sc == rs << 16 + MAX_RESIZERS
                //          true-> 表示当前参与并发扩容的线程达到最大值 65535 - 1
                //          false-> 表示当前线程可以参与进来
                // 条件四:transferIndex <= 0
                //          true-> 说明map 全局范围内的任务已经分配完了,当前线程进去也没活干了
                //          false-> 还有任务可以分配
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || transferIndex <= 0)
                    break;
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
                    transfer(tab, nextTab);
                    break;
                }
            }
            return nextTab;
        }
        return table;
    }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值