Java多线程基础 09.LongAdder原理解析

Java多线程基础 09.LongAdder

  Java 8新增了一个新的类LongAdder,以空间换时间的方式提高高并发场景下CAS操作的性能。

  LongAdder的核心思想是热点分离,与ConcurrentHashMap中的分段锁非常类似。LongAdder将常规的value属性拆成一个数组;并发访问时,通过哈希算法将线程映射到对应的元素进行CAS操作;访问value时,再进行求和计算。

LongAdder和AtomicLong性能比对

测试用例:

public class LongAdderVsAtomicLong {

    public static void main(String[] args) {
        testAtomicLongVSLongAdder(1, 10000000);
        testAtomicLongVSLongAdder(10, 10000000);
        testAtomicLongVSLongAdder(20, 10000000);
    }

    public static void testAtomicLongVSLongAdder(final int threadCount, final int times) {
        try {
            System.out.println("threadCount = " + threadCount + ",times = " + times);
            long start = System.currentTimeMillis();
            testLongAdder(threadCount, times);
            System.out.println("LongAdder elapse:" + (System.currentTimeMillis() - start) + "ms");

            long start2 = System.currentTimeMillis();
            testAtomicLong(threadCount, times);
            System.out.println("AtomicLong elapse:" + (System.currentTimeMillis() - start2) + "ms");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static void testAtomicLong(int threadCount, int times) throws InterruptedException {
        AtomicLong atomicLong = new AtomicLong();
        List<Thread> list = new ArrayList<>();

        for (int i = 0; i < threadCount; i++) {
            list.add(new Thread(() -> {
                for (int j = 0; j < times; j++) {
                    atomicLong.incrementAndGet();
                }
            }));
        }
        for (Thread t : list) {
            t.start();
        }
        for (Thread t : list) {
            t.join();
        }
    }

    private static void testLongAdder(int threadCount, int times) throws InterruptedException {
        LongAdder longAdder = new LongAdder();
        List<Thread> list = new ArrayList<>();

        for (int i = 0; i < threadCount; i++) {
            list.add(new Thread(() -> {
                for (int j = 0; j < times; j++) {
                    longAdder.increment();
                }
            }));
        }
        for (Thread t : list) {
            t.start();
        }
        for (Thread t : list) {
            t.join();
        }
    }

}

输出结果:

threadCount = 1,times = 10000000
LongAdder elapse:136ms
AtomicLong elapse:53ms
threadCount = 10,times = 10000000
LongAdder elapse:183ms
AtomicLong elapse:1821ms
threadCount = 20,times = 10000000
LongAdder elapse:440ms
AtomicLong elapse:3372ms

LongAdder原理解析

  LongAdder的核心思想是热点分离,与ConcurrentHashMap中的分段锁非常类似。LongAdder将常规的value属性拆成一个数组;并发访问时,通过哈希算法将线程映射到对应的元素进行CAS操作;访问value时,再进行求和计算。

  AtomicLong使用内部属性value保持这实际的long值,所有的操作都是针对该value属性进行的。在高并发环境下,value变量是大量线程竞争的一个热点。重试线程越多,就意味着CAS的失败概率越高,从而浪费大量CPU性能。

  LongAdder继承自Striped64, 其中有三个非常重要的属性,如下代码所示:

/**
 * cells的哈希表. 非空时,长度为2的幂。
 */
transient volatile Cell[] cells;

/**
 * 基本值,主要在没有争用时使用,但也用作在哈希表初始化竞争中的一种应变计划。通过CAS更新。
 */
transient volatile long base;

/**
 * 当resize和或创建Cells被使用的自旋锁 (通过CAS实现)。
 */
transient volatile int cellsBusy;

  如果没有发生线程竞争的情况,LongAdder会将要累加的数会通过CAS累加到base中;如果发生线程竞争的情况,会将要累加的数累加到cells数组中的某个Cell元素中。所以LongAdder的sum()方法的实现也必定是将base和cells中的值进行累加,这一点可以通过以下源码得到验证。

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;
}

  LongAdder的base类似于AtomicInteger里面的value,在没有竞争的情况下,cells数组为null,这时只使用base进行累加;而一旦发生竞争,cells数组就进行初始化。

  cells数组第一次初始化长度为2,以后每次扩容都变为原来的两倍,一直到cells数组的长度大于等于当前服务器CPU的核数。同一时刻能持有CPU时间片去并发操作同一个内存地址的最大线程数最多也就是CPU的核数。

  刚才有说到“如果发生线程竞争的情况,会将要累加的数累加到cells数组中的某个Cell元素中。”,在add方法中,会通过“as[Thread.currentThread().threadLocalRandomProbe & (as.length - 1)])”即"as[getProbe() & m])”,将当前线程的threadLocalRandomProbe属性和cells的长度进行与运算来计算出当前线程对应的cell对象的索引。

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);
    }
}

Striped64的更多子类

  在JDK中,Striped64除了LongAdder以外,还有三个子类。这三个子类分别是java.util.concurrent.atomic.LongAccumulatorjava.util.concurrent.atomic.DoubleAdderjava.util.concurrent.atomic.DoubleAccumulator。由于底层实现差不多,此处不再赘述了,感兴趣的同学可以自我学习。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值