第2篇:LongAdder阅读理解

35 阅读10分钟

概述与案例

  • 在上一篇中(第1篇:并发基石CAS),我们分析了CAS相关的问题。
  • 那么我们就可以发现,CAS实现了很多的功能,但是现在网站的流量很大,我想测试一下在高并发场景下的性能。那么我们就分为几种方式去评估。
  • 需求:统计网站访问次数,假设访问100W次,看一下不同线程不同加锁方式使用的时间。代码如下
/**
 * 多线程情况下的比较
 * 
 * @author icanci
 * @since 1.0 Created in 2022/06/06 21:28
 */
public class MoreThreadDemo {
    // 1千万
    protected static final int count = 1000 * 10000;

    protected static int       num   = 0;

    public static void main(String[] args) throws Exception {
        // 线程数量 - 执行多少次( 1000 * 10000 )
        tests(1, count);
        tests(2, count);
        tests(10, count);
        tests(20, count);
        tests(40, count);
        tests(60, count);
        tests(80, count);
        tests(100, count);
    }

    /**
     * test
     *
     * @param threadCount 线程数量
     * @param times 每个线程自增多少次
     */
    private static void tests(final int threadCount, final int times) {

        // synchronized
        staticLog(() -> {
            testSynchronized(threadCount, times);
        }, "synchronized");

        // AtomicInteger
        staticLog(() -> {
            testAtomicInteger(threadCount, times);
        }, "AtomicInteger");

        // AtomicLong
        staticLog(() -> {
            testAtomicLong(threadCount, times);
        }, "AtomicLong");

        // LongAdder
        staticLog(() -> {
            testLongAdder(threadCount, times);
        }, "LongAdder");

        // LongAccumulator
        staticLog(() -> {
            testLongAccumulator(threadCount, times);
        }, "LongAccumulator");
        System.out.println("-------------------- 华丽的分隔线 --------------------");

        // 归零
        num = 0;
    }

    /**
     * 测试 Synchronized
     */
    private static void testSynchronized(int threadCount, int times) {
        try {
            List<Thread> threads = new ArrayList<>();
            CountDownLatch countDownLatch = new CountDownLatch(threadCount);

            for (int i = 0; i < threadCount; i++) {
                threads.add(new Thread(() -> {
                    try {
                        for (int j = 0; j < times; j++) {
                            doSum();
                        }
                    } finally {
                        countDownLatch.countDown();
                    }
                }));
            }
            for (Thread thread : threads) {
                thread.start();
            }
            countDownLatch.await();
            System.out.println("synchronized结果:" + num);
        } catch (Exception e) {
            // 
        }
    }

    private static synchronized void doSum() {
        num++;
    }

    /**
     * 测试 AtomicInteger
     */
    private static void testAtomicInteger(int threadCount, int times) {
        try {
            AtomicInteger atomicInteger = new AtomicInteger();
            List<Thread> threads = new ArrayList<>();
            CountDownLatch countDownLatch = new CountDownLatch(threadCount);
            for (int i = 0; i < threadCount; i++) {
                threads.add(new Thread(() -> {
                    try {
                        for (int j = 0; j < times; j++) {
                            atomicInteger.incrementAndGet();
                        }
                    } finally {
                        countDownLatch.countDown();
                    }
                }));
            }
            for (Thread thread : threads) {
                thread.start();
            }
            countDownLatch.await();
            System.out.println("atomicInteger结果:" + atomicInteger.get());
        } catch (Exception e) {
            // 
        }
    }

    /**
     * 测试 AtomicLong
     */
    private static void testAtomicLong(final int threadCount, final int times) {
        try {
            AtomicLong atomicLong = new AtomicLong();
            List<Thread> threads = new ArrayList<>();
            CountDownLatch countDownLatch = new CountDownLatch(threadCount);
            for (int i = 0; i < threadCount; i++) {
                threads.add(new Thread(() -> {
                    try {
                        for (int j = 0; j < times; j++) {
                            atomicLong.incrementAndGet();
                        }
                    } finally {
                        countDownLatch.countDown();
                    }
                }));
            }
            for (Thread thread : threads) {
                thread.start();
            }
            countDownLatch.await();
            System.out.println("atomicLong结果:" + atomicLong.get());
        } catch (Exception e) {
            // 
        }
    }

    /**
     * 测试 LongAdder
     */
    private static void testLongAdder(int threadCount, int times) {
        try {
            LongAdder longAdder = new LongAdder();
            List<Thread> threads = new ArrayList<>();
            CountDownLatch countDownLatch = new CountDownLatch(threadCount);
            for (int i = 0; i < threadCount; i++) {
                threads.add(new Thread(() -> {
                    try {
                        for (int j = 0; j < times; j++) {
                            longAdder.increment();
                        }
                    } finally {
                        countDownLatch.countDown();
                    }
                }));
            }
            for (Thread thread : threads) {
                thread.start();
            }
            countDownLatch.await();
            System.out.println("longAdder结果:" + longAdder.longValue());
        } catch (Exception e) {
            // 
        }
    }

    /**
     * 测试 LongAccumulator
     */
    private static void testLongAccumulator(int threadCount, int times) {
        try {
            LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y, 0);
            List<Thread> threads = new ArrayList<>();
            CountDownLatch countDownLatch = new CountDownLatch(threadCount);
            for (int i = 0; i < threadCount; i++) {
                threads.add(new Thread(() -> {
                    try {
                        for (int j = 0; j < times; j++) {
                            longAccumulator.accumulate(1);
                        }
                    } finally {
                        countDownLatch.countDown();
                    }
                }));
            }
            for (Thread thread : threads) {
                thread.start();
            }
            countDownLatch.await();
            System.out.println("longAccumulator结果:" + longAccumulator.longValue());
        } catch (Exception e) {
            //
        }
    }

    /**
     * log
     */
    protected static void staticLog(MyConsumer consumer, String name) {
        long start = System.currentTimeMillis();
        consumer.apply();
        long end = System.currentTimeMillis();
        System.out.println(name + " 执行了:" + (end - start) + "ms");
    }
}

@FunctionalInterface
interface MyConsumer {
    // apply
    void apply();
}
  • 代码如上,执行结果如下
synchronized结果:10000000
synchronized 执行了:102ms
atomicInteger结果:10000000
AtomicInteger 执行了:74ms
atomicLong结果:10000000
AtomicLong 执行了:75ms
longAdder结果:10000000
LongAdder 执行了:77ms
longAccumulator结果:10000000
LongAccumulator 执行了:75ms
-------------------- 华丽的分隔线 --------------------
synchronized结果:20000000
synchronized 执行了:411ms
atomicInteger结果:20000000
AtomicInteger 执行了:334ms
atomicLong结果:20000000
AtomicLong 执行了:346ms
longAdder结果:20000000
LongAdder 执行了:76ms
longAccumulator结果:20000000
LongAccumulator 执行了:77ms
-------------------- 华丽的分隔线 --------------------
synchronized结果:100000000
synchronized 执行了:636ms
atomicInteger结果:100000000
AtomicInteger 执行了:3630ms
atomicLong结果:100000000
AtomicLong 执行了:3806ms
longAdder结果:100000000
LongAdder 执行了:142ms
longAccumulator结果:100000000
LongAccumulator 执行了:138ms
-------------------- 华丽的分隔线 --------------------
synchronized结果:200000000
synchronized 执行了:1280ms
atomicInteger结果:200000000
AtomicInteger 执行了:7604ms
atomicLong结果:200000000
AtomicLong 执行了:7353ms
longAdder结果:200000000
LongAdder 执行了:256ms
longAccumulator结果:200000000
LongAccumulator 执行了:271ms
-------------------- 华丽的分隔线 --------------------
synchronized结果:400000000
synchronized 执行了:2670ms
atomicInteger结果:400000000
AtomicInteger 执行了:15345ms
atomicLong结果:400000000
AtomicLong 执行了:15071ms
longAdder结果:400000000
LongAdder 执行了:513ms
longAccumulator结果:400000000
LongAccumulator 执行了:544ms
-------------------- 华丽的分隔线 --------------------
synchronized结果:600000000
synchronized 执行了:4210ms
atomicInteger结果:600000000
AtomicInteger 执行了:23090ms
atomicLong结果:600000000
AtomicLong 执行了:22758ms
longAdder结果:600000000
LongAdder 执行了:839ms
longAccumulator结果:600000000
LongAccumulator 执行了:865ms
-------------------- 华丽的分隔线 --------------------
synchronized结果:800000000
synchronized 执行了:5773ms
atomicInteger结果:800000000
AtomicInteger 执行了:28619ms
atomicLong结果:800000000
AtomicLong 执行了:28628ms
longAdder结果:800000000
LongAdder 执行了:1221ms
longAccumulator结果:800000000
LongAccumulator 执行了:1109ms
-------------------- 华丽的分隔线 --------------------
synchronized结果:1000000000
synchronized 执行了:7343ms
atomicInteger结果:1000000000
AtomicInteger 执行了:37131ms
atomicLong结果:1000000000
AtomicLong 执行了:37012ms
longAdder结果:1000000000
LongAdder 执行了:1314ms
longAccumulator结果:1000000000
LongAccumulator 执行了:1353ms
-------------------- 华丽的分隔线 --------------------
  • 实现方式-加锁
  • 实现方式-AtomicInteger
  • 实现方式-AtomicLong
  • 实现方式-LongAdder
  • 实现方式-LongAccumulator
  • 经过对比之后发现,LongAdder性能非常优秀,尤其是在多线程的场景下。并且LongAccumulator的性能也不错,因为LongAccumulator和LongAdder的实现方式是完全一样的,只是做了一些定制。通过LongBinaryOperator函数,后面我们在看源码的时候会看到,二者唯一的不同就是计算是用的值还是用的LongBinaryOperator来计算。LongAccumulator性能慢一些,是因为Lambda表达式(方法引用)会额外的创建一些对象。

LongAdder图解

  • LongAdder的结构比较简单,如下图

  • 其由base值(long类型)和Cell数组构成,如上图
  • 当没有base的更新没有线程竞争的时候,会直接写到base里面去,而不会操作Cell数组,当base的写出现了竞争的时候,就会创建Cell数组,由不同的线程写不同的下标。当最后求和的时候,通过上述的公式,sum = base + 所有槽的值。
  • 执行流程图

  • 刚开始第一次去理解上述的内容比较抽象,我现在从源码开始讲起。

LongAdder源码

  • LongAdder的属性,在其父类Striped64中
abstract class Striped64 extends Number {
    
    /*
     * @sun.misc.Contended 添加缓存填充,用来消除伪共享
     * 伪共享会导致缓存行失效,缓存一致性开销变大。
     * 伪共享指的是多个线程同时读写同一个缓存行的不同变量时导致的 CPU缓存失效。
     * 尽管这些变量之间没有任何关系,但由于在主内存中邻近,存在于同一个缓存行之中,
     * 它们的相互覆盖会导致频繁的缓存未命中,引发性能下降。这里对于伪共享我只是提一下概念,并不会深入去讲解,大家可以自行查阅一些资料。
     * 解决伪共享的方法一般都是使用直接填充,我们只需要保证不同线程的变量存在于不同的 CacheLine 即可,
     * 使用多余的字节来填充可以做点这一点,这样就不会出现伪共享问题。在Disruptor队列的设计中就有类似设计。
     */
    @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核心数
    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 基础value 值,当并发较低的时候,只累加该值,主要用于没有竞争的情况,通过CAS更新
     */
    transient volatile long base;

    /**
     * Spinlock (locked via CAS) used when resizing and/or creating Cells.
     * 创建或者落入Cells数组的时候使用的自旋锁变量调整单元格大小,创建单元格时候使用的锁
     * 初始化cells或者扩容cells需要获取锁 0表示无锁状态 1表示其他线程持有了锁
     */
    transient volatile int cellsBusy;
        /** Number of CPUS, to place bound on table size */
    // CPU核心数
    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 基础value 值,当并发较低的时候,只累加该值,主要用于没有竞争的情况,通过CAS更新
     */
    transient volatile long base;

    /**
     * Spinlock (locked via CAS) used when resizing and/or creating Cells.
     * 创建或者落入Cells数组的时候使用的自旋锁变量调整单元格大小,创建单元格时候使用的锁
     * 初始化cells或者扩容cells需要获取锁 0表示无锁状态 1表示其他线程持有了锁
     */
    transient volatile int cellsBusy;
}
  • LongAdder#add()方法
  • 我们通常使用的是LongAdder的increment()方法,也就是自增方法,这里LongAdder其实调用的是add()方法
public class LongAdder extends Striped64 implements Serializable { 
    /**
     * Equivalent to {@code add(1)}.
     * 自增 实际上调用的是 add(1L);
     */
    public void increment() {
        add(1L);
    }

    /**
     * Equivalent to {@code add(-1)}.
     * 自减 实际上调用的是 add(1L);
     */
    public void decrement() {
        add(-1L);
    }
}
  • LongAdder继承Striped64,并且使用到了Striped64中的方法,这是一个公共方法,我们稍后再说。
  • 现在来分析LongAdder的add方法
public class LongAdder extends Striped64 implements Serializable { 
    /**
     * Adds the given value.
     *
     * 1.casBase
     * 2.casBase失败,创建cells
     * 3.cells失败,扩容cells,扩容到CPU核心数大小
     *
     * @param x the value to add
     */
    public void add(long x) {
        // as 表示当前cells引用
        // b  表示获取的base值
        // v  表示期望值
        // m  表示cells数组的长度
        // a  表示当前线程命中的cell单元格

        Cell[] as; long b, v; int m; Cell a;
        // 条件1:true 表示cell已经初始化过了 当前线程应该将数据写入到对应的cell中
        //       false 表示第一次加载,所有的数据应该尝试写到base中
        // 条件2:true 表示当前线程CAS数据替换失败 这里指的是 !casBase(x,y) 整体是否为true
        //       false 表示当前线程CAS成功
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            // 进入到方法的前提条件
            // case1: cells已经初始化,需要尝试将数据写入到对应的cell,注意,这里是尝试写入
            // case2: false 发生竞争了,可能需要重试或者扩容

            // ture 未竞争 false 竞争
            boolean uncontended = true;
            // 第一次 as 为null

            // 已经新建了数组了
            // 如果某个calls槽cas失败,就可能进行扩容操作
            // 如果第一次写发现值为null,初始化了cell,但是没有设置值。需要设置值
            // 如果不为null,就进行cas操作
            // 如果不为null,就进行cas操作 且 cas操作失败,然后扩容


            // 条件1:as == null 说明当前cells未初始化
            // 条件2:(m = as.length - 1) < 0 cells数组长度等于0 m = cells数组长度,且长度一定为2的幂,后续会看到或参考HashMap的扩容
            // 条件3:(a = as[getProbe() & m]) == null 当前线程的cells[index] 对应的数据为null,说明是第一次写值
            //       捞一下getProbe():获取当前线程的hash值与上cells长度-1,计算cells中的下标
            // 条件4:!(uncontended = a.cas(v = a.value, v + x)) 当前 a = cells[index] cas写失败 此时 uncontended = false 发生竞争了
            // 满足上述条件都会执行累加

           
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[getProbe() & m]) == null ||
                !(uncontended = a.cas(v = a.value, v + x)))
                // 执行累加 调用父类Striped64的longAccumulate方法
                longAccumulate(x, null, uncontended);
        }
    }
}

Striped64中一些操作方法

  • Striped64提供了数据的CAS操作,和线程的hash处理,如下
abstract class Striped64 extends Number {
    
    /**
     * CASes the base field.
     */
    final boolean casBase(long cmp, long val) {
        // cas 进行更新 base值
        // 返回是否更新成功,CAS只写一次
        // 可能写失败,写失败就开始建立Cells
        return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
    }

    /**
     * CASes the cellsBusy field from 0 to 1 to acquire lock.
     * cas 操作cellsBusy 的值
     */
    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;
    }
    
}

LongAdder父类Striped64的longAccumulate方法

  • 在这个方法中,我们就提到了之前的LongBinaryOperator函数,实际在进行计算的时候,是判断是否有这个值来进行处理的。
abstract class Striped64 extends Number {
     /**
     * 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 // Cells 初始化之后,并当前线程CAS修改失败 为false
     *
     * 如何进来这个方法
     * 1.cells未初始化并且casBase失败
     * 2.cells初始化并且当前线程cas失败
     * 3.cells的对应的值为null,第一次写值
     */
    final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        // h: 线程hash值
        int h;
        // 还没有给线程分配hash值,这个时候先分配一手hash值
        if ((h = getProbe()) == 0) {
            // 等于0 强制设置为当前线程,再去获取 hash 值
            ThreadLocalRandom.current(); // force initialization 强制
            // 重新获取 probe 值 ,hash值被设置为一个全新的线程,所以设置了 wasUncontended 为true
            h = getProbe();
            // 重新计算了,认为不是热点 也即是说,线程第一次进来,计算的hash值为0,当前线程的hash值0与任何数据位运算都是0,
            // 那么就会落到第一个cell,这是不合理的,所以需要将wasUncontended设置为true
            // 也就是说,首次线程访问,先会写cell[0]号位置,写成功了,就没有下文了,写失败了,说明发生了竞争,那么就不适合再给线程设置为0了,因为已经发生了抢夺
            wasUncontended = true;
        }
        // collide 表示扩容意向,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 已经初始化了 则操作 cells
            if ((as = cells) != null && (n = as.length) > 0) {
                // CASE1.1: 如果计算完成之后,计算当前的cell单元为null,说明这个cell没有被使用
                // 那么就需要新建cell
                if ((a = as[(n - 1) & h]) == null) {
                    // cellsBusy == 0 锁没有被占用
                    // 如果cells数组没有再扩容
                    if (cellsBusy == 0) {       // Try to attach new Cell
                        // 创建一个cell单元
                        Cell r = new Cell(x);   // Optimistically create
                        // 尝试加锁
                        if (cellsBusy == 0 && casCellsBusy()) {
                            boolean created = false;
                            // 在有锁的情况下在检查一下之前的判断
                            try {               // Recheck under lock
                                // 创建并且使用
                                Cell[] rs; int m, j;
                                // 条件1和条件2恒成立,因为进来的时候就已经判断了
                                // 这里大家可能会有疑惑,为毛没有再判断一次 cells == as
                                // 因为这个时候,两个线程A、B进来之后,A停在加锁之前,B进来了,那么A、B必然插入的是同一个槽
                                // 那么这个时候会判断 rs[j = (m - 1) & h] == null) 如果为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;
                            // 这个时候目标槽已经不是空了 继续自旋,这个时候这个地方就进不来了
                            // LongAdder 必须保证每个值都写进去
                            continue;           // Slot is now non-empty
                        }
                    }
                    // 扩容意向修改为false
                    // 因为当前线程还没写值,不一定写失败,所以扩容意向为false
                    collide = false;
                }
                // CASE1.2:只有一种情况会进来,cells初始化之后,当前线程竞争修改失败,并且已经初始化线程hash值
                // wasUncontended = false 这里是重新设置这个值为 true
                // 紧接着执行 advanceProbe(h) 重置当前线程的hash,继续循环
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                    // 如果为true。就继续执行当前cell位的cas操作
                    // CASE1.3:当前线程rehash过hash值,新命中的cell不为空,就来到这里,然后进行cas写
                    //          如果写成功了,跳出循环
                    //          如果写失败了,表示rehash之后,又写失败了,进入下一个条件
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                        fn.applyAsLong(v, x))))
                    break;
                    // CASE1.4:
                    // 条件1:n 是数组长度 大于CPU核心数,collide 设置为false,扩容意向不搞了,
                    // 条件2:cells != as 其他线程已经扩容过了,当前线程重新hash
                else if (n >= NCPU || cells != as)
                    // 如果n大于等于 NCPU 不可扩容
                    collide = false;            // At max size or stale
                    // CASE1.5:
                    // 设置扩容意向为true,但是不一定扩容成功
                    // 如果扩容意向为false,重新设置为true 然后继续执行循环
                    // 设置为true之后,再次rehash然后再次循环,如果又失败了,这里且不满足 (n >= NCPU || cells != as)
                    // 此时collide为true 执行 CASE1.6
                else if (!collide)
                    collide = true;
                    // CASE1.6:扩容操作并加锁 注意:只是扩容,并没有进行赋值
                    // 条件1:cellsBusy == 0 表示当前没有占用锁,当前线程可以抢锁
                    // 条件2: casCellsBusy() 抢锁
                else if (cellsBusy == 0 && casCellsBusy()) {
                    try {
                        // 和上面一样,需要判断
                        if (cells == as) {      // Expand table unless stale
                            // 扩容2倍
                            Cell[] rs = new Cell[n << 1];
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            // 扩容赋值
                            cells = rs;
                        }
                    } finally {
                        // 释放锁
                        cellsBusy = 0;
                    }
                    // 扩容意向设置为false
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                // 重新设置线程hash值 h
                h = advanceProbe(h);
            }
            // CASE2: cells 没加锁并且没有初始化(cells为null),则尝试进行加锁并且开始初始化操作
            // 条件1:cellsBusy == 0 没有加锁
            // 条件2:cells == as 为什么又对比一次?因为多线程场景下,A线程过来,发现是null,然后进入了,然后B线程过来,不一定是null
            //       可能其他线程会修改cells
            // 条件3:表示cas获取锁成功,将cellsBusy设置为1,失败表示其他线程正在持有锁
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                // 初始化操作
                boolean init = false;
                try {                           // Initialize table
                    // 初始化的时候,进行创建cells数组
                    // casBase 失败,新建大小为2的cells数组
                    // 为毛又对比一下?因为是这样的,有可能在执行 cellsBusy == 0 && cells == as && casCellsBusy() 的时候
                    // 执行到 cellsBusy == 0 && cells == as 线程让出CPU 然后另外一个线程执行为true,那么其可以继续操作cells
                    // 此时在切换过来,这个cells就不是null了

                    // 类似双重检查锁 - 这里是安全的,因为多线程场景下只有一个线程能够拿到锁,使用casCellsBusy()保证
                    if (cells == as) {
                        // 新建大小为2的cells数组
                        Cell[] rs = new Cell[2];
                        // 设置值
                        rs[h & 1] = new Cell(x);
                        // 赋值cells
                        cells = rs;
                        // 初始化成功
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            // CASE3:cells 正在初始化 则尝试在base上进行累加
            // 1.当前cellsBusy被别的线程持有,这个时候需要进行兜底操作,不能让灵魂无处安放
            // 2.cells被其他线程初始化,需要累加到base
            else if (casBase(v = base, ((fn == null) ? v + x :
                    fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }
    
}

LongAdder其他方法

  • 当没有base的更新没有线程竞争的时候,会直接写到base里面去,而不会操作Cell数组,当base的写出现了竞争的时候,就会创建Cell数组,由不同的线程写不同的下标。当最后求和的时候,通过上述的公式,sum = base + 所有槽的值。
public class LongAdder extends Striped64 implements Serializable {

    // 求和方法,只保证最终一致性
    // 计算方式如上
    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;
    }

    // 数值归零方法
    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;
            }
        }
    }

    // 数值归零并返回sum值
    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;
    }
}

总结

  • 基本上LongAdder就是这样,比较简单,核心在于父类的longAccumulate方法
  • LongAdder的思想很重要,并且对并发的处理也很重要。后续在ConcurrentHashMap中还会遇到类似的逻辑和设计。