LongAdder 源码解析(基于 JDK 1.8)

23 篇文章 1 订阅

文章目录


LongAdder 和 LongAccumulator 基本一致,区别在于前者默认是加法,后者会同时传入一个表达式,具体结果是通过二元表达式计算得到的。

DoubleAdder 和 DoubleAccumulator 没有做什么,只是使用 Double.longBitsToDoubleDouble.doubleToRawLongBits进行 Long 和 Double 的转换,剩下的和前面两个类一样。

1 LongAdder

LongAdder 继承了 Striped64,可以实现高并发下的 Long 的计数,它和 AtomicLong 的区别在于:LongAdder 只保证最终一致性,也就是多个线程一起做加法,只保证都能把想写的数据写入,并不保证在中间的时刻都能看到,以及求和得到正确结果。

LongAdder 把数据保存在两个部分,base 和 数组 cells,如果并发量不大,直接写入 base;否则一部分结果会写入数组 cells。求和的时候只需要将 base 和 cells 的结果相加即可。这两个成员变量继承自 Striped64。

下面的方法中,最难理解的是 add,这里需要详细解释。

和上篇文章一样,仍然需要理解短路机制:对于逻辑运算符 a || b,如果 a 为 true,会直接返回 true,不会处理 b;否则,会处理 b,并返回 b 的结果。

  1. 首先判断 as 是否为空,如果不空,会进入下面的代码块;否则,说明 as 是空,即 as 还未初始化,尝试将结果写入,如果写入成功,那就不用继续操作了;如果写入失败,则进入下面的代码块

  2. 记 uncontended 为 true, uncontended 翻译为“无竞争”。

  3. 判断 as 是否为空;如果不空,检查是否长度为0;如果长度不是0,则获取 cells 的某一个位置,并判断是否初始化。

    这三个判断如果有一个为 true,注意到 uncontended 一直是 true,调用 longAccumulate。

    如果前三个都是 false,进入第四个 cas 的处理,如果写入成功,就结束了,不执行 longAccumulate;否则,说明写入失败,uncontended 为 false,调用 longAccumulate。

直观上的理解,有两次写入:先写入base;如果写入 base 失败,会尝试写入某一个 cell;如果还失败,就需要 longAccumulate。这个在后面再讲。

public class LongAdder extends Striped64 implements Serializable {
    private static final long serialVersionUID = 7249069246863182397L;

    /**
     * Creates a new adder with initial sum of zero.
     */
    public LongAdder() {
    }

    /**
     * Adds the given value.
     *
     * @param x the value to add
     */
    public void add(long x) {
        Cell[] as; long b, v; int m; Cell a;
        // 1. 判断 cells 和 进行 casBase 操作
       // casBase 是 Striped64 的方法,用于修改 base 的值。
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            boolean uncontended = true;
          	// getProbe 表示随机获取一个值
            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);
        }
    }

    /**
     * Equivalent to {@code add(1)}.
     */
    public void increment() {
        add(1L);
    }

    /**
     * Equivalent to {@code add(-1)}.
     */
    public void decrement() {
        add(-1L);
    }

    /**
     * Returns the current sum.  The returned value is <em>NOT</em> an
     * atomic snapshot; invocation in the absence of concurrent
     * updates returns an accurate result, but concurrent updates that
     * occur while the sum is being calculated might not be
     * incorporated.
     *
     * @return the sum
     */
   // 求和,如果 cells 里面有值就会加到 sum 中。
    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;
    }

    /**
     * Resets variables maintaining the sum to zero.  This method may
     * be a useful alternative to creating a new adder, but is only
     * effective if there are no concurrent updates.  Because this
     * method is intrinsically racy, it should only be used when it is
     * known that no threads are concurrently updating.
     */
  // 将所有位置 base 和 cells 的每一个部分恢复为0
    public void reset() {
        Cell[] as = cells; Cell a;
        base = 0L;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    a.value = 0L;
            }
        }
    }

    /**
     * Equivalent in effect to {@link #sum} followed by {@link
     * #reset}. This method may apply for example during quiescent
     * points between multithreaded computations.  If there are
     * updates concurrent with this method, the returned value is
     * <em>not</em> guaranteed to be the final value occurring before
     * the reset.
     *
     * @return the sum
     */
   //一边求和,一边将对应位置恢复为0
    public long sumThenReset() {
        Cell[] as = cells; Cell a;
        long sum = base;
        base = 0L;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null) {
                    sum += a.value;
                    a.value = 0L;
                }
            }
        }
        return sum;
    }
    ...
}

2 Striped64

前面的 LongAdder,在 add 方法中使用了 longAccumulate,这个最重要的方法在 Striped64 中。

在讲这个方法之前,先介绍一下别的方法和变量。

@sun.misc.Contended 表示使用了缓存行填充。启动时必须加上参数生效 XX:-RestrictContended

每个 CPU 都有自己的缓存。缓存与主内存进行数据交换的基本单位叫Cache Line(缓存行)。在64位x86架构中,缓存行是64字节,也就是8个Long型的大小。这也意味着当缓存失效,要刷新到主内存的时候,最少要刷新64字节。

如果是图 1,则X 失效时,Y和Z 也会失效,这称为伪共享;为了将它们分开,可以将它们三个分别填充上 7 个 Long,这样就会每个变量占一个缓存行,如图2,防止了伪共享问题。

图1

图1

图2

图2

内部类 Cell,有一个 volatile long value,以及相应的 cas 方法。

对于 base,cellsbusy 和 probe,表示对应的值或者数组;大写的三个单词表示偏移量。

abstract class Striped64 extends Number {

    /**
     * Padded variant of AtomicLong supporting only raw accesses plus CAS.
     *
     * JVM intrinsics note: It would be possible to use a release-only
     * form of CAS here, if it were provided.
     */
    @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;
                valueOffset = UNSAFE.objectFieldOffset
                    (ak.getDeclaredField("value"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }

    /** Number of CPUS, to place bound on table size */
  	// CPU 的个数,限制 cells 表的最大长度为:
    //满足  (>=NCPU && 长度为2的幂) 的最小值
    static final int NCPU = Runtime.getRuntime().availableProcessors();

    /**
     * Table of cells. When non-null, size is a power of 2.
     */
  	// 非空时,cells 要求长度为2的幂
    transient volatile Cell[] cells;

    /**
     * Base value, used mainly when there is no contention, but also as
     * a fallback during table initialization races. Updated via CAS.
     */
  	// base
    transient volatile long base;

    /**
     * Spinlock (locked via CAS) used when resizing and/or creating Cells.
     */
  	// 在扩容或者写入的时候作为锁,平时为0,加锁时为1
    transient volatile int cellsBusy;

    /**
     * Package-private default constructor
     */
    Striped64() {
    }

    /**
     * CASes the base field.
     */
   // cas修改 base 
    final boolean casBase(long cmp, long val) {
        return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
    }

    /**
     * CASes the cellsBusy field from 0 to 1 to acquire lock.
     */
  	// cas修改 cellbusy,将 0 变成 1,表示加锁 
    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.
     */
    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.
     */
  	// 做一个伪随机
    static final int advanceProbe(int probe) {
        probe ^= probe << 13;   // xorshift
        probe ^= probe >>> 17;
        probe ^= probe << 5;
        UNSAFE.putInt(Thread.currentThread(), PROBE, probe);
        return probe;
    }
		...
    // Unsafe mechanics
    private static final sun.misc.Unsafe UNSAFE;
    // 注意,下面的三个大写的单词,表示偏移量
  	// 小写的单词表示对应的值或者数组
    private static final long BASE;
    private static final long CELLSBUSY;
    private static final long PROBE;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> sk = Striped64.class;
            BASE = UNSAFE.objectFieldOffset
                (sk.getDeclaredField("base"));
            CELLSBUSY = UNSAFE.objectFieldOffset
                (sk.getDeclaredField("cellsBusy"));
            Class<?> tk = Thread.class;
            PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }

}

下面在讲解时,同一级别的互斥关系 if/else if/else 用同一等级的序号表示,比如:1/2/3 ;1.1/1.2/1.3;对于同一级别但不具有互斥关系的if/else/try/catch,会重新使用前面的序号

具体操作如下:

首先判断 h 是否为0,如果为0,在下面执行 h & (n-1) 时只能得到0,所以重新初始化,并重新获取 h,将 wasUncontended 修改为 true。

记 collide 为 false,执行下面的 for 循环

1 (表不空的情况) 如果 as 非空,且长度大于0

1.1 如果 as 在计算出的位置 (n-1) & h 为空

1.1.1 如果 cellsBusy 为0,也就是没有上锁。新建一个值大小为 x 的cell

1.1.1.1 再次检查,如果 cellsBusy 不为空,且 casCellsBusy 执行成功。定义 created 为 false,尝试将这个 cell 放入。

1.1.1.1.1 执行 try 操作,在内部检查 cells 不空 && 长度大于 0 && 对应位置的 cell 为 null,则将对应的值放入,并修改 created 为 true;

1.1.1.1.2 在 finally 中,释放锁 cellsBusy。

下面的 if 和 try-catch 是并列关系,但不具备类似 if 和 else 的互斥关系,故重新从 1 开始排序

1.1.1.1.1 如果 created 为 true,表示新的 cell 放入成功,退出整个循环,结束。

1.1.1.1.2 (相当于写了 else)会跳过本次循环,执行下次 for 循环。

1.1 如果之前既没有 break,也没有 continue,会修改 collide 为false。

1.2 如果 wasUncontended 为 false,表示在 LongAdder 中使用 cas 修改 cell 失败,将其修改为 true。

1.3 执行 cas 操作,修改 cell 为 a 的值,分两种情况,如果是 LongAdder,传入的函数是 null,表示简单相加;如果是 LongAccumulator,会根据传入的表达值进行计算。如果 cas 成功,退出整个循环。

1.4 两种情况,如果 n >= NCPU,会将 collide 设置为 false(但由于长度 n 不会减小,该表达式一直成立,所以在接下来的循环中,要么在前面的判断语句中执行了,要么就再次进入 1.4,空转);如果是 cells != as,表示读到的数据 as 已经过期,此时也将 collide 设置为 false。

1.5 如果 collide 为 false,则修改为 true。

1.6 如果 cellsBusy 为0,执行 casCellsBusy 成功

1.6.1 执行 try,判断 cells 是否还是 as,是的话,将 cell 数组扩容,注意,新扩容出来的位置 rs[n]-rs[2*n-1] 没有初始化,节省时间,留待以后的线程执行 1.1.1.1.2 来创建。在 finally 中释放锁 cellsBusy。

1.6 修改 collide 为 false,跳过本次循环

1 执行 advanceProbe ,重新计算 h,使 (n - 1) & h 变成新的位置。类似于别的类中的 rehash。

2 (初始化) 如果 cellsBusy 为0,且 cells == as,cas 修改 busy 成功,定义 init 为 false

2.1 在 try 中,再次判断 cells == as,如果是,则新建一个长度为2的表,并将新的 Cell(x) 放入某个位置,设置 init 为 true

2.2 在 finally 中,释放锁 cellsBusy

下面的 if 和 try-catch 是并列关系,但不具备类似 if 和 else 的互斥关系,故重新从 1 开始排序

2.1 如果 init 为 true,说明写入成功,退出整个循环

3 (写入 base) 尝试直接写入到 base 中,写入成功同样退出循环。

还有几个问题需要解释一下

  1. cellsBusy 为0表示未加锁,为1表示加锁,无论扩容还是新建,都需要加锁和解锁。

  2. collide 意思为“碰撞”,一般为 false 时表示出现碰撞。

  3. 如果 1.2-1.5 为 false,并不是空转,实际上在这一行h = advanceProbe(h),修改了h,使得后面再次循环处理时写入的位置会发生变化,避开冲突位置。

  4. 双重检验:在代码中,反复出现内外两个 if 判断 cellsBusy == 0(如 1.1.1 和1.1.1.1) 以及 cells ==as(如 2 和2.1 的 if),这称为双重检验。由于在多线程中,代码执行可能会被打断,在计入内部循环前,可能锁状态或者 cells 数组已经发生变化,所以需要再次检查。这方面著名的例子是单例模式的懒汉式的双重检验。

  5. cellsBusy == 0 && casCellsBusy()并不多余,前面的操作快,后面的慢,之所以执行前面的判断是为了尽量减少执行慢的 cas 操作。

final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        int h;
  			// 如果为0,需要重新生成
        if ((h = getProbe()) == 0) {
            ThreadLocalRandom.current(); // force initialization
            h = getProbe();
            wasUncontended = true;
        }
        boolean collide = false;                // True if last slot nonempty
        for (;;) {
            Cell[] as; Cell a; int n; long v;
            // 1 cells不空
            if ((as = cells) != null && (n = as.length) > 0) {
              	// 1.1  as 对应位置的 cell 不空 
                if ((a = as[(n - 1) & h]) == null) {
                  	// 1.1.1 还没有加锁
                    if (cellsBusy == 0) {       // Try to attach new Cell
                        Cell r = new Cell(x);   // Optimistically create
                      	// 1.1.1.1 再次检查是否加锁,并尝试加锁
                        if (cellsBusy == 0 && casCellsBusy()) {
                            boolean created = false;
                          	// 1.1.1.1.1 尝试放入新的 cell
                            try {               // Recheck under lock
                                Cell[] rs; int m, j;
                              	// 如果表不空,且当前 cell 为空
                              	// 尝试放入
                                if ((rs = cells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & h] == null) {
                                    rs[j] = r;
                                    created = true;
                                }
                              //1.1.1.1.2 解锁
                            } finally {
                              	
                                cellsBusy = 0;
                            }
                          	//新的 1.1.1.1.1 ,表示已经成功放入 r,可以退出
                            if (created)
                                break;
                          	//省略了else,1.1.1.1.2 ,否则,没有成功,继续下次循环
                            continue;           // Slot is now non-empty
                        }
                    }
                  	// 同样是 1.1
                    collide = false;
                }
                // 1.2 表示 LongAdder 或者 LongAccumulator 在 cas 写入 cell 失败
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
              	// 1.3 尝试写入 a 中
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x))))
                    break;
              	// 1.4 如果 n>=NCPU,修改collide,且无法到下面扩容分支
              	// 如果数据变旧即 cells != as 修改 collide
                else if (n >= NCPU || cells != as)
                    collide = false;            // At max size or stale
              	// 1.5 如果 collide 为false,设置为 true
                else if (!collide)
                    collide = true;
              	// 1.6 如果没加锁尝试加锁
                else if (cellsBusy == 0 && casCellsBusy()) {
                  	// 1.6.1 // 如果数据没有变旧,尝试扩容
                    try {                      	
                        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
                }
              	// 这个属于第一个if,即 1 ,重新计算 h
                h = advanceProbe(h);
            }
          	// 2 如果没加锁且 as 是最新数据,尝试加锁
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                boolean init = false;
              	// 2.1 再次检查 as 是否是最新数据
                try {                           // Initialize table
                    if (cells == as) {
                        Cell[] rs = new Cell[2];
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                  //2.2 释放锁
                } finally {
                    cellsBusy = 0;
                }
              	// 重新排列后的2.1,如果 init,说明新建成功
                if (init)
                    break;
            }
          	// 3	尝试直接写入 base,如果成功则退出
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }

下面是我的公众号,Java与大数据进阶,分享 Java 与大数据笔面试干货,欢迎关注
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值