ConcurrentHashMap第三讲:addCount方法

目录

在读addCount源码之前一定要把LongAdder 源码读完(下面是链接)
LongAdder源码解析

addCount内部使用的是LongAdder 而不是 AtomicLong,主要原因是因为效率问题。

在这里插入图片描述
LongAdder:总数 = base + 所有的cell

	* 求sum的一个方法,不是准确值,是期望值,因为其它线程可能还在写数据
	* 把cells求和,再加上base就是总和sum。
   final long sumCount() {
        CounterCell[] as = counterCells; CounterCell a;
        long sum = baseCount;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
        return sum;
    }

addCount:

   private final void addCount(long x, int check) {
        // as 表示 LongAdder.cells
        // b 表示 LongAdder.base
        // s 表示当前map.table中元素的数量
        CounterCell[] as; long b, s;
        //条件一:true-> 表示cells 已经初始化了,当前线程应该去使用hash寻址找到合适的cell 去累加数据
        //      false-> 表示当前线程应该将数据累加到base
        //条件二:false-> 表示写base成功,数据累加到base 中了,当前竞争不强烈,不需要创建cells
        //      true-> 表示写base失败,与其它线程在base上发生竞争,当前线程应该去尝试创建cells
        /**
         * LongAdder 中的cekks数组,当baseCount 发生竞争后,会创建cells数组
         * 线程会通过计算hash值 取道自己的cell,将增量累加到指定cell中
         * 总数 = sum(cells) + baseCount
         *
         * private transient volatile CounterCell[] counterCells;
         */
        if ((as = counterCells) != null ||
            !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
            //有几种情况进入到if中
            // 1. true-> 表示cells 已经初始化了,当前线程应该去使用hash寻址找到合适的cell 去累加数据
            // 2. true-> 表示写base失败,与其它线程在base上发生竞争,当前线程应该去尝试创建cells

            //a 表示当前线程hash寻址命中的cell
            CounterCell a;
            //v 表示当前线程写cell 时的期望值
            long v;
            //m 表示当前cells 数组的长度
            int m;
            //true-> 未竞争 false->发生竞争
            boolean uncontended = true;

            //条件一:as == null || (m = as.length - 1) < 0
            //      表示写base竞争失败,然后进入if块,需要调用fullAddCount进行扩容 或者重试 LongAdder.longAccumulate
            //条件二:(a = as[ThreadLocalRandom.getProbe() & m]) == null
            //      前置条件:cells已经初始化了,
            //      true-> 表示当前线程命中的cell表格是个空,需要当前线程进入fullAddCount方法去初始化 cells,放入当前位置
            //条件三:!(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
            //      false->取反得到false,表示当前线程使用cas方式更新当前命中的cell成功
            //      true-> 取反得到true,表示当前线程使用cas方式更新命中的cell失败,需要进入fullAddCount 进行重试或者扩容cells
            if (as == null || (m = as.length - 1) < 0 ||
                    //getProbe() 获取当前线程的hash值
                (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
                !(uncontended =
                  U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
                // 和LongAdder的 longAccumulate一样
                fullAddCount(x, uncontended);
                //考虑到fullAddCount 里面的事情太多,就让当前线程不参与到扩容相关的逻辑了
                return;
            }
            //  check是 是否扩容的主要标识
            // putVal方法中调用addCount时, 里面传进来的binCount = check
            // binCount >= 1  当前命中的桶位的链表的长度,是1时也可能代表key相同,发生冲突
            // binCount == 0  当前命中的桶位是null,直接将节点放到桐中
            // binCount == 2  桶位下已经树化
            // remove() 方法中调用addCount时, 里面传进来的 check=-1
            if (check <= 1)
                return;
            // 获取当前散列表的元素个数,期望值
            s = sumCount();
        }
        // 表示一定是一个put 操作调用的addCount (只有添加元素时才会扩容)
        if (check >= 0) {
            // tab 代表 map.table
            // nt 代表 map.nextTable
            /**
             * 扩容过程中,会将扩容中的新table 赋值给 nextTable 保持引用,扩容结束之后,这里会被设置为Null
             *  private transient volatile Node<K, V>[] nextTable;
             */
            // n 代表table 数组的长度
            // sc 代表sizeCtl 的临时值
            Node<K,V>[] tab, nt; int n, sc;

            /**     sizeCtl < 0
             *  X   1. -1 表示当前table正在初始化(有线程在创建table数组),当前线程需要自旋等待..
             * 可能  2. 表示当前mao正在进行扩容 高16位表示:扩容的标识戳   低16位表示:(1 + nThread) 当前参与并发扩容的线程数量
             *
             *      sizeCtl = 0
             *  X   表示创建table数组时,使用 DEFAULT_CAPACITY 为大小
             *
             *      sizeCtl > 0
             *  X   1.如果table 未初始化,表示初始化大小
             * 可能  2.如果已经初始化,表示下次扩容时的 触发条件(阈值)
             */
            // 自旋
            // 条件一:s >= (long)(sc = sizeCtl)
            // true:1.当前sizeCtl 为一个负数,表示正在扩容中。
            //       2.当前sizeCtl 是一个正数,表示扩容阈值
            // false: 表示当前table 尚未达到扩容条件
            // 条件二; (tab = table) != null 恒成立
            // 条件三: (n = tab.length) < MAXIMUM_CAPACITY
            //          当前table长度小于最大值限制,则可以进行扩容
            while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                   (n = tab.length) < MAXIMUM_CAPACITY) {
                //当前扩容标识戳,之前讲过
                //16 -> 32 标识戳:32768
                int rs = resizeStamp(n);
                // 条件成立:当前table正在扩容
                //   当前线程理论上应该协助table 完成扩容
                if (sc < 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-> 表示当前线程可以参与进来
                    // 条件四:(nt = nextTable) == null
                    //          true-> 表示本次扩容结束
                    //          false-> 扩容正在进行
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                        transferIndex <= 0)
                        break;
                    // 前置条件:当前table 正在执行扩容中,当前线程有机会参与扩容
                    //   条件成立:说明当前线程成功参与到扩容任务中,并且将sc低16位加1,表示多了一个线程参与工作
                    //   条件失败:说明参与工作的线程比较多,cas修改失败,下次自旋  大概率还会来到这里
                    //   条件失败:1.当前很多线程都在此处尝试修改sizeCtl,有其它一个线程修成功,导致你的sc期望值与内存中的值不一致,修改失败
                    //           2.transfer  任务内部的线程也修改了sizeCtl
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        // 协助扩容线程,持有nextTable 参数
                        //在transfer 方法中,需要做一些扩容准备工作
                        transfer(tab, nt);
                }
                //RESIZE_STAMP_SHIFT = 16
                //      1000 0000 0001 1011   0000 0000 0000 0000 + 2
                // =>   1000 0000 0001 1011   0000 0000 0000 0010
                // 条件成立:说明当前线程是触发扩容的第一个线程,在transfer 方法中,需要做一些扩容准备工作
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    // 触发扩容条件的线程,不持有nextTable 参数
                    transfer(tab, null);

				// 再次获取当前散列表的元素个数,期望值,再次自旋
                s = sumCount();
            }
        }
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值