并发编程——LongAdder

1 LongAdder结构分析

1.1 为什么要有LongAdder

AutomicLong底层使用了CAS操作来控制并发的。在并发量级比较小的情况下,线程冲突的概率比较小,自旋次数少。但是,高并发的情况下,多个线程同时进行自旋操作,就会出现大量失败并一直自旋的情况,这个时候AutomicLong的性能就下降了。所以引入了LongAdder,解决高并发环境下AtomicLong自旋瓶颈的问题。

1.2 Longadder的结构

其结构如下,当线程不存在竞争的时候,首先将值写入到base中,当线程之间有竞争时会通过和HashMap一样的哈希算法,内部维护了一个base值和一个cell数组,写入到cell数组的一个槽中。

 

 

 2 源码分析

LongAdder类本身,只有一个空参构造和一个add()方法,主要继承了Striped64并实现了Serializable

  public LongAdder() {
  }

 

    public void add(long x) {
        //as 表示cells引用
        //b 表示获取的base值
        //v 表示 期望值
        //m 表示 cells 数组的长度
        //a 表示当前线程命中的cell单元格
        Cell[] as; long b, v; int m; Cell a;

        //条件一:true->表示cells已经初始化过了,当前线程应该将数据写入到对应的cell中
        //       false->表示cells未初始化,当前所有线程应该将数据写到base中

        //条件二:false->表示当前线程cas替换数据成功,
        //       true->表示发生竞争了,可能需要重试 或者 扩容
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            //什么时候会进来?
            //1.true->表示cells已经初始化过了,当前线程应该将数据写入到对应的cell中
            //2.true->表示发生竞争了,可能需要重试 或者 扩容



            //true -> 未竞争  false->发生竞争
            boolean uncontended = true;

       // 这里把as == null || (m = as.length - 1) < 0作为一个条件处理
//条件一:true->说明 cells 未初始化,也就是多线程写base发生竞争了 // false->说明 cells 已经初始化了,当前线程应该是 找自己的cell 写值 //条件二:getProbe() 获取当前线程的hash值 m表示cells长度-1 cells长度 一定是2的次方数 15= b1111 // true-> 说明当前线程对应下标的cell为空,需要创建 longAccumulate 支持 // false-> 说明当前线程对应的cell 不为空,说明 下一步想要将x值 添加到cell中。 //条件三:true->表示cas失败,意味着当前线程对应的cell 有竞争 // false->表示cas成功 if (as == null || (m = as.length - 1) < 0 || (a = as[getProbe() & m]) == null || !(uncontended = a.cas(v = a.value, v + x))) //都有哪些情况会调用? //1.true->说明 cells 未初始化,也就是多线程写base发生竞争了[重试|初始化cells] //2.true-> 说明当前线程对应下标的cell为空,需要创建 longAccumulate 支持 //3.true->表示cas失败,意味着当前线程对应的cell 有竞争[重试|扩容] longAccumulate(x, null, uncontended); } }

 add(0方法里面有个longAccumulate()方法,源码如下:

  //都有哪些情况会调用?
    //1.true->说明 cells 未初始化,也就是多线程写base发生竞争了[重试|初始化cells]
    //2.true-> 说明当前线程对应下标的cell为空,需要创建 longAccumulate 支持
    //3.true->表示cas失败,意味着当前线程对应的cell 有竞争[重试|扩容]

    // wasUncontended:只有cells初始化之后,并且当前线程 竞争修改失败,才会是false
    final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        //h 表示线程hash值
        int h;
        //条件成立:说明当前线程 还未分配hash值
        if ((h = getProbe()) == 0) {
            //给当前线程分配hash值
            ThreadLocalRandom.current(); // force initialization
            //取出当前线程的hash值 赋值给h
            h = getProbe();
            //为什么? 因为默认情况下 当前线程 肯定是写入到了 cells[0] 位置。 不把它当做一次真正的竞争
            wasUncontended = true;
        }

        //表示扩容意向 false 一定不会扩容,true 可能会扩容。
        boolean collide = false;                // True if last slot nonempty

        //自旋
        for (;;) {
            //as 表示cells引用
            //a 表示当前线程命中的cell
            //n 表示cells数组长度
            //v 表示 期望值
            Cell[] as; Cell a; int n; long v;

            //CASE1: 表示cells已经初始化了,当前线程应该将数据写入到对应的cell中
            if ((as = cells) != null && (n = as.length) > 0) {
                //2.true-> 说明当前线程对应下标的cell为空,需要创建 longAccumulate 支持
                //3.true->表示cas失败,意味着当前线程对应的cell 有竞争[重试|扩容]

                //CASE1.1:true->表示当前线程对应的下标位置的cell为null,需要创建new Cell
                if ((a = as[(n - 1) & h]) == null) {

                    //true->表示当前锁 未被占用  false->表示锁被占用
                    if (cellsBusy == 0) {       // Try to attach new Cell

                        //拿当前的x创建Cell
                        Cell r = new Cell(x);   // Optimistically create

                        //条件一:true->表示当前锁 未被占用  false->表示锁被占用
                        //条件二:true->表示当前线程获取锁成功  false->当前线程获取锁失败..
                        if (cellsBusy == 0 && casCellsBusy()) {
                            //是否创建成功 标记
                            boolean created = false;
                            try {               // Recheck under lock
                                //rs 表示当前cells 引用
                                //m 表示cells长度
                                //j 表示当前线程命中的下标
                                Cell[] rs; int m, j;

                                //条件一 条件二 恒成立
                                //rs[j = (m - 1) & h] == null 为了防止其它线程初始化过 该位置,然后当前线程再次初始化该位置
                                //导致丢失数据
                                if ((rs = cells) != null &&
                                        (m = rs.length) > 0 &&
                                        rs[j = (m - 1) & h] == null) {
                                    rs[j] = r;
                                    created = true;
                                }
                            } finally {
                                cellsBusy = 0;
                            }
                            if (created)
                                break;
                            continue;           // Slot is now non-empty
                        }
                    }

                    //扩容意向 强制改为了false
                    collide = false;
                }
                // CASE1.2:
                // wasUncontended:只有cells初始化之后,并且当前线程 竞争修改失败,才会是false
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                //CASE 1.3:当前线程rehash过hash值,然后新命中的cell不为空(因为在case1里面的最后有个h = advanceProb()方法,不管执行case1的哪个条件,都会重新计算)
                //true -> 写成功,退出循环
                //false -> 表示rehash之后命中的新的cell 也有竞争 重试1次 再重试1次
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                        fn.applyAsLong(v, x))))
                    break;
                //CASE 1.4:
                //条件一:n >= NCPU 条件成立->扩容意向 改为false,表示不扩容了  条件不成立-> 说明cells数组还可以扩容
                //条件二:cells != as true->其它线程已经扩容过了,当前线程rehash之后重试即可
                else if (n >= NCPU || cells != as)
                //扩容意向 改为false,表示不扩容了
                    collide = false;            // At max size or stale
                //CASE 1.5:
                //!collide = true 设置扩容意向 为true 但是不一定真的发生扩容
                else if (!collide)
                    collide = true;
                //CASE 1.6:真正扩容的逻辑
                //条件一:cellsBusy == 0 true->表示当前无锁状态,当前线程可以去竞争这把锁
                //条件二:casCellsBusy true->表示当前线程 获取锁 成功,可以执行扩容逻辑
                // false->表示当前时刻有其它线程在做扩容相关的操作。
                else if (cellsBusy == 0 && casCellsBusy()) {
                    try {
                        //cells == as
                        if (cells == as) {      // Expand table unless stale
                            Cell[] rs = new Cell[n << 1];
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            cells = rs;
                        }
                    } finally {
                        //释放锁
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }

                //重置当前线程Hash值
                h = advanceProbe(h);
            }
            //CASE2:前置条件cells还未初始化 as 为null
            //条件一:true 表示当前未加锁
            //条件二:为什么在上一个if条件里面已经有了cellas再次判断?因为其它线程可能会在你给as赋值之后修改了 cells
            //条件三:拿锁的操作:true 表示获取锁成功 会把cellsBusy = 1,false 表示其它线程正在持有这把锁
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                boolean init = false;
                try {                           // Initialize table
                    //cells == as? 防止其它线程已经初始化了,当前线程再次初始化 导致丢失数据
                    if (cells == as) {
                        Cell[] rs = new Cell[2];
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            //CASE3:
            //1.当前cellsBusy加锁状态,表示其它线程正在初始化cells,所以当前线程将值累加到base
            //2.cells被其它线程初始化后,当前线程需要将数据累加到base
            else if (casBase(v = base, ((fn == null) ? v + x :
                    fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }

 现在来总结一下longAccumulate()方法的逻辑

首先都有哪些情况会调用?

1.true->说明 cells 未初始化,也就是多线程写base发生竞争了[初始化cells]
  进入case2,当前线程拿到锁并进入扩容;
  如果当前线程没有获取到锁或者其他线程已经初始化了,则进入case3,表示其它线程正在初始化cells,所以当前线程将值累加到base
2、3都会进入case1,表示cells已经初始化了,当前线程应该将数据写入到对应的cell中
  2.true-> 说明当前线程对应下标的cell为空,需要创建 longAccumulate 支持
    进入case1.1,创建new Cell
  3.true->表示cas失败,意味着当前线程对应的cell 有竞争[重试|扩容]
    cell已经有了但是在写的时候发生了竞争,进入case1.2,这时wasUncontended为false,取反为true,进入该条件,将这个值设置为true,进行rehash,
    自旋重新进入case1,cell不为空的话进入case1.3尝试一次,成功则退出自旋循环,否则进入case1.5,将collide设置为true,再次rehash,
    再次查看case1.3条件是否满足,不满足则会直接进入到case1.6,这里才是真正的扩容方法。
注意:这里的case1.4是数组的长度不能超过cpu的数量,因为一旦超过,cpu同一时间只能处理同个线程,超过的部分是浪费的。
posted @ 2020-10-18 10:09  Mistolte  阅读(318)  评论(0编辑  收藏  举报