多线程(六)LongAdder源码

12 篇文章 0 订阅

1 LongAdder结构分析

在这里插入图片描述

1.1 为什么要有LongAdder

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

1.2 Longadder的结构

其结构如下,当线程不存在竞争的时候,首先将值写入到base中,当线程之间有竞争时会通过和HashMap一样的哈希算法,内部维护了一个base值和一个cell数组,写入到cell数组的一个槽中。
在这里插入图片描述把一个变量拆成多份,变为多个变量,有些类似于 ConcurrentHashMap 的分段锁的例子。如下图所示,把一个Long型拆成一个base变量外加多个Cell,每个Cell包装了一个Long型变量。当多个线程并发累加的时候,如果并发度低,就直接加到base变量上;如果并发度高,冲突大,平摊到这些Cel上。在最后取值的时候,再把base和这些Cell求sum运算。
在这里插入图片描述

2 源码分析

2.1 LongAdder源码

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

public class LongAdder extends Striped64 implements Serializable {
    private static final long serialVersionUID = 7249069246863182397L;
    public LongAdder() {
    }
    public void add(long x) {
        //....
    }
  • 分析add方法
public void add(long x) {
        Cell[] as; long b, v; int m; Cell a;
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            boolean uncontended = true;
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[getProbe() & m]) == null ||
                !(uncontended = a.cas(v = a.value, v + x)))
                longAccumulate(x, null, uncontended);
        }
    }

在这里插入图片描述

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

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

        //条件二(!casBase(b = base, b + x)):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;条件二:(a = as[getProbe() & m]) == null;条件三:!(uncontended = a.cas(v = a.value, v + x))
            //条件一:true->说明 cells 未初始化,说明上面那个if的条件二为true,也就是多线程写base发生竞争了
            //       false->说明 cells 已经初始化了,当前线程应该是 找自己的cell 写值

            //条件二:说明:getProbe()可以理解成获取当前线程的hash值;m表示cells长度减1,cells长度一定是2的次方数
            //             [getProbe() & m]就类似HashMap的"(length-1) & hash"。cells长度一定是2的次方的原因也类似HashMap 的长度为什么是2的幂次方
            //       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);
        }
    }
分析:
   ①1表示第一个if的第一个条件;①2表示第一个if的第二个条件
   ②1表示第二个if的第一个条件;②2表示第二个if的第二个条件;②3表示第二个if的第三个条件;
       如果,①1true,表示当前线程需要把数写到对应的cell中。来到第二个if,②1false,
           如果②2false,计算出a(当前线程命中的cell单元格),然后在②3中将数据通过cas的方式添加,
               如果添加成功,即②3false,add方法结束
               如果添加失败,即②3true,就调用longAccumulate方法重试|扩容

   如果,①2false,表示当前线程cas替换数据成功。add方法结束
   如果,①2true,表示当前线程cas替换数据的时候发生竞争了,可能需要重试 或者 扩容

    //返回当前总和。
    //在没有并发更新的情况下调用会返回准确的结果,但在计算总和时发生的并发更新可能不会合并。
    public long sum() {
        Cell[] as = cells; Cell a;
        long sum = base;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
        return sum;
    }

2.2 Striped64源码

  • 内部类Cell
    @sun.misc.Contended static final class Cell {
        volatile long value;
        Cell(long x) { value = x; }
        final boolean cas(long cmp, long val) {
            return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
        }
        // Unsafe mechanics
        private static final sun.misc.Unsafe UNSAFE;
        //某个成员变量在对应的类中的内存偏移量(该变量在内存中的位置),表示该成员变量本身。
        private static final long valueOffset;
        static {
            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class<?> ak = Cell.class;
                //Unsafe的objectFieldOffset(...) 方法调用,就是为了找到AtomicInteger类中value属性所在的内存偏移量。
                valueOffset = UNSAFE.objectFieldOffset
                        (ak.getDeclaredField("value"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }
    /** CPU 数量,用于限制表大小 */
    //表示当前计算机CPU数量,什么用? 控制cells数组长度的一个关键条件
    static final int NCPU = Runtime.getRuntime().availableProcessors();
    /**
     * Base value, used mainly when there is no contention, but also as
     * a fallback during table initialization races. Updated via CAS.
     * 没有发生过竞争时,数据会累加到 base上 | 当cells扩容过程中,线程就不能往对应的单元格中写,需要将数据写到base中
     */
    transient volatile long base;
    /**
     * 调整大小或创建单元格时使用的自旋锁(通过 CAS 锁定).
     * 初始化cells或者扩容cells都需要获取锁,0 表示无锁状态,1 表示其他线程已经持有锁了
     */
    transient volatile int cellsBusy;
    /**
     * CASes the cellsBusy field from 0 to 1 to acquire lock.
     * 通过CAS方式获取锁
     */
    final boolean casCellsBusy() {
        return UNSAFE.compareAndSwapInt(this, CELLSBUSY, 0, 1);
    }
    /**
     * Returns the probe value for the current thread.
     * Duplicated from ThreadLocalRandom because of packaging restrictions.
     * 获取当前线程的Hash值
     */
    static final int getProbe() {
        return UNSAFE.getInt(Thread.currentThread(), PROBE);
    }
    /**
     * Pseudo-randomly advances and records the given probe value for the
     * given thread.
     * Duplicated from ThreadLocalRandom because of packaging restrictions.
     * 重置当前线程的Hash值
     */
    static final int advanceProbe(int probe) {
        probe ^= probe << 13;   // xorshift
        probe ^= probe >>> 17;
        probe ^= probe << 5;
        UNSAFE.putInt(Thread.currentThread(), PROBE, probe);
        return probe;
    }
  • 重点理解longAccumulate方法

  • LongAdder中调用此方法的原因:(三者之一)

    1. 情况一:cells 未初始化,也就是多线程写base发生竞争了 [重试|初始化cells]
    1. 情况二:cells 已初始化,但是当前线程对应下标的cell为空,需要创建 longAccumulate 支持
    1. 情况三:表示cas失败,意味着当前线程对应的cell 有竞争 [重试|扩容]
  • 说一下longAccumulate()方法的逻辑,都有哪些情况会调用?

1. 情况一:说明 cells 未初始化,也就是多线程写base发生竞争了[初始化cells]
       会进入case2,当前线程拿到锁并进入扩容;
     如果当前线程没有获取到锁或者其他线程已经初始化了,则进入case3,表示其它线程正在初始化cells,所以当前线程将值累加到base
2. 情况二、情况三都会进入case1,表示cells已经初始化了,当前线程应该将数据写入到对应的cell中
      情况二:说明当前线程对应下标的cell为空,需要创建 longAccumulate 支持
           进入case1.1,创建new Cell
     情况三:表示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同一时间只能处理一个线程,超过的无法处理,会造成内存浪费
 /**
     * Handles cases of updates involving initialization, resizing,
     * creating new Cells, and/or contention. See above for
     * explanation. This method suffers the usual non-modularity
     * problems of optimistic retry code, relying on rechecked sets of
     * reads.
     *
     * @param x the value
     * @param fn the update function, or null for add (this convention
     * avoids the need for an extra field or function in LongAdder).
     * @param wasUncontended false if CAS failed before call
     */
    //都有哪些情况会调用?
    //1.true-> cells 未初始化,也就是多线程写base发生竞争了[初始化cells]
    //         执行CASE2
    //2.true-> cells 已初始化,但是当前线程对应下标的cell为空,需要创建 longAccumulate 支持
    //         执行CASE1.1
    //3.true-> cas失败,意味着当前线程对应的cell 有竞争[重试|扩容]
    //         执行CASE1.3 失败 -> CASE1.5 -> rehash,即执行advanceProbe(h) -> 自旋 -> 执行CASE1.3 再失败 -> CASE1.6()因为CASE1.5中将设置扩容意向为true

    // 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();
            //为什么wasUncontended设置为true?
            //代码执行到这里,说明线程没有分配hash值(hash=0)
            //所以 当前线程 肯定是写入到了 cells[0] 位置(0 & 任意数 = 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) {
                //进入这个if,有下面两种情况
                //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;

                                //条件一 条件二 为true
                                //条件三: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:true -> wasUncontended:只有cells初始化之后,并且当前线程 竞争修改失败,才会是false
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                //CASE 1.3:当前线程rehash过hash值,然后新命中的cell不为空
                //true -> 写成功,退出循环
                //false -> 表示rehash之后命中的新的cell 也有竞争
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                        fn.applyAsLong(v, x))))
                    break;
                //CASE 1.4:
                //条件一:n >= NCPU 为 true->就会把扩容意向改为false,不再扩容; 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。  防止其它线程已经扩容了,当前线程再次扩容。cells的引用就会发生变化,导致数据流失
                        if (cells == as) {      // Expand table unless stale
                            //数组长度扩容,得到新数组,新长度等于旧长度左移1位,等价于 新长度=旧长度*2
                            Cell[] rs = new Cell[n << 1];
                            //遍历旧数组,将旧数组对应位置的值放到新数组中
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            //将新数组的引用赋值给全局的cells
                            cells = rs;
                        }
                    } finally {
                        //释放锁
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }

                //修改失败,就会重置当前线程Hash值,然后当前线程自旋,再次修改时,就会到不同的cell上
                h = advanceProbe(h);
            }
            //CASE2:前置条件cells还未初始化,as 为null
            //条件一:true 表示当前未加锁
            //条件二:为什么cells == as?短路原则,当判断这个条件时,条件一为true,表示其他线程已经持有锁了
            //       那么其它线程在执行下面的初始化方法,可能会在你给as赋值为null之后修改了 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:前面两个if中所有条件都为false才会执行到这,说明:
            //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
        }
    }

在这里插入图片描述

流程图

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值