前言

思维导图.png

文章中所有高清无码图片公众号号回复: 图片666 即可查阅, 可直接关注公众号:壹枝花算不算浪漫

最近阿里巴巴发布了Java开发手册(泰山版) (公众号回复: 开发手册 可收到阿里巴巴开发手册(泰山版 2020.4.22发布).pdf),其中第17条写到:

阿里巴巴开发手册.png

对于Java项目中计数统计的一些需求,如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)

在大多数项目及开源组件中,计数统计使用最多的仍然还是AtomicLong,虽然是阿里巴巴这样说,但是我们仍然要根据使用场景来决定是否使用LongAdder

今天主要是来讲讲LongAdder实现原理,还是老方式,通过图文一步步解开LongAdder神秘的面纱,通过此篇文章你会了解到:

  • 为什么AtomicLong在高并发场景下性能急剧下降?
  • LongAdder为什么快?
  • LongAdder实现原理(图文分析)
  • AtomicLong是否可以被遗弃或替换?

本文代码全部基于JDK 1.8,建议边看文章边看源码更加利于消化

AtomicLong

当我们在进行计数统计的时,通常会使用AtomicLong来实现。AtomicLong能保证并发情况下计数的准确性,其内部通过CAS来解决并发安全性的问题。

AtomicLong实现原理

说到线程安全的计数统计工具类,肯定少不了Atomic下的几个原子类。AtomicLong就是juc包下重要的原子类,在并发情况下可以对长整形类型数据进行原子操作,保证并发情况下数据的安全性。

public class AtomicLong extends Number implements java.io.Serializable {
    public final long incrementAndGet() {
        return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
    }     public final long decrementAndGet() {
        return unsafe.getAndAddLong(this, valueOffset, -1L) - 1L;
    }
}

我们在计数的过程中,一般使用incrementAndGet()decrementAndGet()进行加一和减一操作,这里调用了Unsafe类中的getAndAddLong()方法进行操作。

接着看看unsafe.getAndAddLong()方法:

public final class Unsafe {
    public final long getAndAddLong(Object var1, long var2, long var4) {
        long var6;
        do {
            var6 = this.getLongVolatile(var1, var2);
        } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));         return var6;
    }     public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
}

这里直接进行CAS+自旋操作更新AtomicLong中的value值,进而保证value值的原子性更新

AtomicLong瓶颈分析

如上代码所示,我们在使用CAS + 自旋的过程中,在高并发环境下,N个线程同时进行自旋操作,会出现大量失败并不断自旋的情况,此时AtomicLong的自旋会成为瓶颈。

AtomicLong瓶颈分析.png

如上图所示,高并发场景下AtomicLong性能会急剧下降,我们后面也会举例说明。

那么高并发下计数的需求有没有更好的替代方案呢?在JDK8Doug Lea大神 新写了一个LongAdder来解决此问题,我们后面来看LongAdder是如何优化的。

LongAdder

LongAdder和AtomicLong性能测试

我们说了很多LongAdder上性能优于AtomicLong,到底是不是呢?一切还是以代码说话:

/**
 * Atomic和LongAdder耗时测试
 *
 * @author:一枝花算不算浪漫
 * @date:2020-05-12 7:06
 */
public class AtomicLongAdderTest {
    public static void main(String[] args) throws Exception{
        testAtomicLongAdder(1, 10000000);
        testAtomicLongAdder(10, 10000000);
        testAtomicLongAdder(100, 10000000);
    }     static void testAtomicLongAdder(int threadCount, int times) throws Exception{
        System.out.println("threadCount: " + threadCount + ", times: " + times);
        long start = System.currentTimeMillis();
        testLongAdder(threadCount, times);
        System.out.println("LongAdder 耗时:" + (System.currentTimeMillis() - start) + "ms");
        System.out.println("threadCount: " + threadCount + ", times: " + times);
        long atomicStart = System.currentTimeMillis();
        testAtomicLong(threadCount, times);
        System.out.println("AtomicLong 耗时:" + (System.currentTimeMillis() - atomicStart) + "ms");
        System.out.println("----------------------------------------");
    }     static void testAtomicLong(int threadCount, int times) throws Exception{
        AtomicLong atomicLong = new AtomicLong();
        List<Thread> list = Lists.newArrayList();
        for (int i = 0; i < threadCount; i++) {
            list.add(new Thread(() -> {
                for (int j = 0; j < times; j++) {
                    atomicLong.incrementAndGet();
                }
            }));
        }         for (Thread thread : list) {
            thread.start();
        }         for (Thread thread : list) {
            thread.join();
        }         System.out.println("AtomicLong value is : " + atomicLong.get());
    }     static void testLongAdder(int threadCount, int times) throws Exception{
        LongAdder longAdder = new LongAdder();
        List<Thread> list = Lists.newArrayList();
        for (int i = 0; i < threadCount; i++) {
            list.add(new Thread(() -> {
                for (int j = 0; j < times; j++) {
                    longAdder.increment();
                }
            }));
        }         for (Thread thread : list) {
            thread.start();
        }         for (Thread thread : list) {
            thread.join();
        }         System.out.println("LongAdder value is : " + longAdder.longValue());
    }
}

执行结果:

CAS原理图.png

这里可以看到随着并发的增加,AtomicLong性能是急剧下降的,耗时是LongAdder的数倍。至于原因我们还是接着往后看。

LongAdder为什么这么快

先看下LongAdder的操作原理图:

YUnlDO.png

既然说到LongAdder可以显著提升高并发环境下的性能,那么它是如何做到的?

1、 设计思想上,LongAdder采用"分段"的方式降低CAS失败的频次

这里先简单的说下LongAdder的思路,后面还会详述LongAdder的原理。

我们知道,AtomicLong中有个内部变量value保存着实际的long值,所有的操作都是针对该变量进行。也就是说,高并发环境下,value变量其实是一个热点数据,也就是N个线程竞争一个热点。

LongAdder的基本思路就是分散热点,将value值的新增操作分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个value值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。

LongAdder有一个全局变量volatile long base值,当并发不高的情况下都是通过CAS来直接操作base值,如果CAS失败,则针对LongAdder中的Cell[]数组中的Cell进行CAS操作,减少失败的概率。

例如当前类中base = 10,有三个线程进行CAS原子性的+1操作线程一执行成功,此时base=11线程二、线程三执行失败后开始针对于Cell[]数组中的Cell元素进行+1操作,同样也是CAS操作,此时数组index=1index=2Cellvalue都被设置为了1.

执行完成后,统计累加数据:sum = 11 + 1 + 1 = 13,利用LongAdder进行累加的操作就执行完了,流程图如下:

分段加锁思路.png

如果要获取真正的long值,只要将各个槽中的变量值累加返回。这种分段的做法类似于JDK7ConcurrentHashMap的分段锁。

2、使用Contended注解来消除伪共享

LongAdder 的父类 Striped64 中存在一个 volatile Cell[] cells; 数组,其长度是2 的幂次方,每个Cell都使用 @Contended 注解进行修饰,而@Contended注解可以进行缓存行填充,从而解决伪共享问题。伪共享会导致缓存行失效,缓存一致性开销变大。

@sun.misc.Contended static final class Cell {

}

伪共享指的是多个线程同时读写同一个缓存行的不同变量时导致的 CPU缓存失效。尽管这些变量之间没有任何关系,但由于在主内存中邻近,存在于同一个缓存行之中,它们的相互覆盖会导致频繁的缓存未命中,引发性能下降。这里对于伪共享我只是提一下概念,并不会深入去讲解,大家可以自行查阅一些资料。

解决伪共享的方法一般都是使用直接填充,我们只需要保证不同线程的变量存在于不同的 CacheLine 即可,使用多余的字节来填充可以做点这一点,这样就不会出现伪共享问题。例如在Disruptor队列的设计中就有类似设计(可参考我之前的博客文章:Disruptor学习笔记):

缓存行填充代码.png 缓存行填充.png

Striped64类中我们可以看看Doug LeaCell上加的注释也有说明这一点:

Cell注释.png

红框中的翻译如下:

Cell类是AtomicLong添加了padded(via@sun.misc.compended)来消除伪共享的变种版本。缓存行填充对于大多数原子来说是繁琐的,因为它们通常不规则地分散在内存中,因此彼此之间不会有太大的干扰。但是,驻留在数组中的原子对象往往彼此相邻,因此在没有这种预防措施的情况下,通常会共享缓存行数据(对性能有巨大的负面影响)。

3、惰性求值

LongAdder只有在使用longValue()获取当前累加值时才会真正的去结算计数的数据,longValue()方法底层就是调用sum()方法,对baseCell数组的数据累加然后返回,做到数据写入和读取分离。

AtomicLong使用incrementAndGet()每次都会返回long类型的计数值,每次递增后还会伴随着数据返回,增加了额外的开销。

LongAdder实现原理

之前说了,AtomicLong是多个线程针对单个热点值value进行原子操作。而LongAdder是每个线程拥有自己的槽,各个线程一般只对自己槽中的那个值进行CAS操作

比如有三个线程同时对value增加1,那么value = 1 + 1 + 1 = 3

但是对于LongAdder来说,内部有一个base变量,一个Cell[]数组。
base变量:非竞态条件下,直接累加到该变量上
Cell[]数组:竞态条件下,累加个各个线程自己的槽Cell[i]中
最终结果的计算是下面这个形式:

value = base + aaarticlea/png;base64,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" alt="">

LongAdder源码剖析

前面已经用图分析了LongAdder高性能的原理,我们继续看下LongAdder实现的源码:

public class LongAdder extends Striped64 implements Serializable {
    public void increment() {
        add(1L);
    }     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);
        }
    }     final boolean casBase(long cmp, long val) {
        return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
    }
}

一般我们进行计数时都会使用increment()方法,每次进行+1操作increment()会直接调用add()方法。

变量说明:

  • as 表示cells引用
  • b 表示获取的base值
  • v 表示 期望值,
  • m 表示 cells 数组的长度
  • a 表示当前线程命中的cell单元格

条件分析:

条件一:as == null || (m = as.length - 1) < 0
此条件成立说明cells数组未初始化。如果不成立则说明cells数组已经完成初始化,对应的线程需要找到Cell数组中的元素去写值。

条件一.png

条件二:(a = as[getProbe() & m]) == null
getProbe()获取当前线程的hash值,m表示cells长度-1,cells长度是2的幂次方数,原因之前也讲到过,与数组长度取模可以转化为按位与运算,提升计算性能。

当条件成立时说明当前线程通过hash计算出来数组位置处的cell为空,进一步去执行longAccumulate()方法。如果不成立则说明对应的cell不为空,下一步将要将x值通过CAS操作添加到cell中。

条件三:!(uncontended = a.cas(v = a.value, v + x)
主要看a.cas(v = a.value, v + x),接着条件二,说明当前线程hash与数组长度取模计算出的位置的cell有值,此时直接尝试一次CAS操作,如果成功则退出if条件,失败则继续往下执行longAccumulate()方法。

条件二/条件三.png

接着往下看核心的longAccumulate()方法,代码很长,后面会一步步分析,先上代码:

java.util.concurrent.atomic.Striped64.:

final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) {
    int h;
    if ((h = getProbe()) == 0) {
        ThreadLocalRandom.current();
        h = getProbe();
        wasUncontended = true;
    }
    boolean collide = false;
    for (;;) {
        Cell[] as; Cell a; int n; long v;
        if ((as = cells) != null && (n = as.length) > 0) {
            if ((a = as[(n - 1) & h]) == null) {
                if (cellsBusy == 0) {
                    Cell r = new Cell(x);
                    if (cellsBusy == 0 && casCellsBusy()) {
                        boolean created = false;
                        try {
                            Cell[] rs; int m, j;
                            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;
                    }
                }
                collide = false;
            }
            else if (!wasUncontended)
                wasUncontended = true;
            else if (a.cas(v = a.value, ((fn == null) ? v + x : fn.applyAsLong(v, x))))
                break;
            else if (n >= NCPU || cells != as)
                collide = false;
            else if (!collide)
                collide = true;
            else if (cellsBusy == 0 && casCellsBusy()) {
                try {
                    if (cells == as) {
                        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;
            }
            h = advanceProbe(h);
        }
        else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
            boolean init = false;
            try {
                if (cells == as) {
                    Cell[] rs = new Cell[2];
                    rs[h & 1] = new Cell(x);
                    cells = rs;
                    init = true;
                }
            } finally {
                cellsBusy = 0;
            }
            if (init)
                break;
        }
        else if (casBase(v = base, ((fn == null) ? v + x : fn.applyAsLong(v, x))))
            break;                          
    }
}

代码很长,if else分支很多,除此看肯定会很头疼。这里一点点分析,然后结合画图一步步了解其中实现原理。

我们首先要清楚执行这个方法的前置条件,它们是或的关系,如上面条件一、二、三

  1. cells数组没有初始化
  2. cells数组已经初始化,但是当前线程对应的cell数据为空
  3. cells数组已经初始化, 当前线程对应的cell数据为空,且CAS操作+1失败

longAccumulate()方法的入参:

  • long x 需要增加的值,一般默认都是1
  • LongBinaryOperator fn 默认传递的是null
  • wasUncontended竞争标识,如果是false则代表有竞争。只有cells初始化之后,并且当前线程CAS竞争修改失败,才会是false

然后再看下Striped64中一些变量或者方法的定义:

  • base: 类似于AtomicLong中全局的value值。在没有竞争情况下数据直接累加到base上,或者cells扩容时,也需要将数据写入到base上
  • collide:表示扩容意向,false 一定不会扩容,true可能会扩容。
  • cellsBusy:初始化cells或者扩容cells需要获取锁, 0:表示无锁状态 1:表示其他线程已经持有了锁
  • casCellsBusy(): 通过CAS操作修改cellsBusy的值,CAS成功代表获取锁,返回true
  • NCPU:当前计算机CPU数量,Cell数组扩容时会使用到
  • getProbe(): 获取当前线程的hash值
  • advanceProbe(): 重置当前线程的hash值

接着开始正式解析longAccumulate()源码:

private static final long PROBE;

if ((h = getProbe()) == 0) {
    ThreadLocalRandom.current();
    h = getProbe();
    wasUncontended = true;
} static final int getProbe() {
    return UNSAFE.getInt(Thread.currentThread(), PROBE);
}

我们上面说过getProbe()方法是为了获取当前线程的hash值,具体实现是通过UNSAFE.getInt()实现的,PROBE是在初始化时候获取当前线程threadLocalRandomProbe的值。

注:Unsafe.getInt()有三个重载方法getInt(Object o, long offset)、getInt(long address) 和getIntVolatile(long address),都是从指定的位置获取变量的值,只不过第一个的offset是相对于对象o的相对偏移量,第二个address是绝对地址偏移量。如果第一个方法中o为null是,offset也会被作为绝对偏移量。第三个则是带有volatile语义的load读操作。

如果当前线程的hash值h=getProbe()为0,0与任何数取模都是0,会固定到数组第一个位置,所以这里做了优化,使用ThreadLocalRandom为当前线程重新计算一个hash值。最后设置wasUncontended = true,这里含义是重新计算了当前线程的hash后认为此次不算是一次竞争。hash值被重置就好比一个全新的线程一样,所以设置了竞争状态为true

可以画图理解为:

wasUncontended设置说明.png

接着执行for循环,我们可以把for循环代码拆分一下,每个if条件算作一个CASE来分析:

final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) {

    for (;;) {
        Cell[] as; Cell a; int n; long v;
        if ((as = cells) != null && (n = as.length) > 0) {         }
        else if (cellsBusy == 0 && cells == as && casCellsBusy()) {         }
        else if (casBase(v = base, ((fn == null) ? v + x : fn.applyAsLong(v, x))))     }
}

如上所示,第一个if语句代表CASE1,里面再有if判断会以CASE1.1这种形式来讲解,下面接着的else ifCASE2, 最后一个为CASE3

CASE1执行条件
if ((as = cells) != null && (n = as.length) > 0) {

}

cells数组不为空,且数组长度大于0的情况会执行CASE1CASE1的实现细节代码较多,放到最后面讲解。

CASE2执行条件和实现原理
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
    boolean init = false;
        try {
            if (cells == as) {
                Cell[] rs = new Cell[2];
                rs[h & 1] = new Cell(x);
                cells = rs;
                init = true;
            }
        } finally {
            cellsBusy = 0;
        }
        if (init)
            break;
}

CASE2 标识cells数组还未初始化,因为判断cells == as,这个代表当前线程到了这里获取的cells还是之前的一致。我们可以先看这个case,最后再回头看最为麻烦的CASE1实现逻辑。

cellsBusy上面说了是加锁的状态,初始化cells数组和扩容的时候都要获取加锁的状态,这个是通过CAS来实现的,为0代表无锁状态,为1代表其他线程已经持有锁了。cells==as代表当前线程持有的数组未进行修改过,casCellsBusy()通过CAS操作去获取锁。但是里面的if条件又再次判断了cell==as,这一点是不是很奇怪?通过画图来说明下问题:

cells==as双重判断说明.png

如果上面条件都执行成功就会执行数组的初始化及赋值操作, Cell[] rs = new Cell[2]表示数组的长度为2,rs[h & 1] = new Cell(x) 表示创建一个新的Cell元素value是x值,默认为1。

h & 1类似于我们之前HashMap或者ThreadLocal里面经常用到的计算散列桶index的算法,通常都是hash & (table.len - 1),这里就不做过多解释了。 执行完成后直接退出for循环

CASE3执行条件和实现原理
else if (casBase(v = base, ((fn == null) ? v + x : fn.applyAsLong(v, x))))
    break;

进入到这里说明cells正在或者已经初始化过了,执行caseBase()方法,通过CAS操作来修改base的值,如果修改成功则跳出循环,这个CASE只有在初始化Cell数组的时候,多个线程尝试CAS修改cellsBusy加锁的时候,失败的线程会走到这个分支,然后直接CAS修改base数据。

CASE1 实现原理

分析完了CASE2和CASE3,我们再折头回看一下CASE1,进入CASE1的前提是:cells数组不为空,已经完成了初始化赋值操作。

接着还是一点点往下拆分代码,首先看第一个判断分支CASE1.1

if ((a = as[(n - 1) & h]) == null) {
    if (cellsBusy == 0) {
        Cell r = new Cell(x);
        if (cellsBusy == 0 && casCellsBusy()) {
            boolean created = false;
            try {
                Cell[] rs; int m, j;
                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;
        }
    }
    collide = false;
}

这个if条件中(a = as[(n - 1) & h]) == null代表当前线程对应的数组下标位置的cell数据为null,代表没有线程在此处创建Cell对象。

接着判断cellsBusy==0,代表当前锁未被占用。然后新创建Cell对象,接着又判断了一遍cellsBusy == 0,然后执行casCellsBusy()尝试通过CAS操作修改cellsBusy=1,加锁成功后修改扩容意向collide = false;

for (;;) {
    if ((rs = cells) != null && (m = rs.length) > 0 && rs[j = (m - 1) & h] == null) {
        rs[j] = r;
        created = true;
    }     if (created)
        break;
    continue;
}

上面代码判断当前线程hash后指向的数据位置元素是否为空,如果为空则将cell数据放入数组中,跳出循环。如果不为空则继续循环。

CASE1.1.png

继续往下看代码,CASE1.2

else if (!wasUncontended)
    wasUncontended = true; h = advanceProbe(h);

wasUncontended表示cells初始化后,当前线程竞争修改失败wasUncontended =false,这里只是重新设置了这个值为true,紧接着执行advanceProbe(h)重置当前线程的hash,重新循环。

接着看CASE1.3

else if (a.cas(v = a.value, ((fn == null) ? v + x : fn.applyAsLong(v, x))))
    break;

进入CASE1.3说明当前线程对应的数组中有了数据,也重置过hash值,这时通过CAS操作尝试对当前数中的value值进行累加x操作,x默认为1,如果CAS成功则直接跳出循环。

CASE1.3.png

接着看CASE1.4:

else if (n >= NCPU || cells != as)
    collide = false;    

如果cells数组的长度达到了CPU核心数,或者cells扩容了,设置扩容意向collide为false并通过下面的h = advanceProbe(h)方法修改线程的probe再重新尝试

至于这里为什么要提出和CPU数量做判断的问题:每个线程会通过线程对cells[threadHash%cells.length]位置的Cell对象中的value做累加,这样相当于将线程绑定到了cells中的某个cell对象上,如果超过CPU数量的时候就不再扩容是因为CPU的数量代表了机器处理能力,当超过CPU数量时,多出来的cells数组元素没有太大作用。

多线程更新Cell.png

接着看CASE1.5

 else if (!collide)
   collide = true;

如果扩容意向collidefalse则修改它为true,然后重新计算当前线程的hash值继续循环,在CASE1.4中,如果当前数组的长度已经大于了CPU的核数,就会再次设置扩容意向collide=false,这里的意义是保证扩容意向为false后不再继续往后执行CASE1.6的扩容操作。

接着看CASE1.6分支:

else if (cellsBusy == 0 && casCellsBusy()) {
    try {
        if (cells == as) {
            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;
}

这里面执行的其实是扩容逻辑,首先是判断通过CAS改变cellsBusy来尝试加锁,如果CAS成功则代表获取锁成功,继续向下执行,判断当前的cells数组和最先赋值的as是同一个,代表没有被其他线程扩容过,然后进行扩容,扩容大小为之前的容量的两倍,这里用的按位左移1位来操作的。

Cell[] rs = new Cell[n << 1];

扩容后再将之前数组的元素拷贝到新数组中,释放锁设置cellsBusy = 0,设置扩容状态,然后继续循环执行。

到了这里,我们已经分析完了longAccumulate()所有的逻辑,逻辑分支挺多,仔细分析看看其实还是挺清晰的,流程图如下:

流程图.png

我们再举一些线程执行的例子里面场景覆盖不全,大家可以按照这种模式自己模拟场景分析代码流程:

多线程执行示例.png

如有问题也请及时指出,我会第一时间更正,不胜感激!

LongAdder的sum方法

当我们最终获取计数器值时,我们可以使用LongAdder.longValue()方法,其内部就是使用sum方法来汇总数据的。

java.util.concurrent.atomic.LongAdder.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;
}

实现很简单,base + aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAALYAAABCCAYAAAD68ywLAAAPSElEQVR4Xu2dCdDV0xvHnxQtBqk3yTZitJhR0avGkgjZSxTtU5KtREpFJmQppbJnGSFpT6KVaTHEVKZso40KISEqS4jM58ycO+e97v2d8/vd333fe997nhnz7/3f33LOc773Oc95nu/z3Ar79u3bJ168BsqZBip4YJezFfXTURrwwPZAKJca8MAul8vqJ5V1YG/cuFEeffRR+eeff+Syyy6Tiy++WL755huZOXOmfPrpp1K9enU588wzpXXr1lK1alW/Il4DsWggq8D+66+/pG3btnLRRRfJkiVL5Oeff1Z/z5kzR4G5VatW8tprr8nHH38sffr0kauuuiqWSfmHeA1kFdhvvvmmTJw4UV5++WW59dZb5cMPP1QaHzp0qLLQyPvvvy9DhgyRI444QqZMmeJXxGsgFg1kFdh9+/aV4uJi6datm7LOSKdOneSGG25IDH7ZsmVy9913y6GHHqqstxevgTg0kFVgY6GPO+44+e677+S6665T4wW8gFgL/verr76qXJMHHnggjjn5Z3gNlE64b8aMGfLEE0/I0UcfLZMmTUqo/d9//5UrrrhC+d79+/eXyy+/3C+J10AsGsiqxdYjvOOOO+S9995TIL7lllsSA//kk08EdwWZNWuWFBUVxTIp/xCvgawDmzCf9q+HDx8uLVu2TGj9scceU4A+5ZRTZNy4cerwuG7dOrn33nv9yngNZKSBrAN7w4YN0rt375T+NQfJb7/9VkVMLrzwQmXRr732WrnyyiszmpS/2Wsg68CePXu2PPLII3L88cfLhAkTSmhcA3vkyJGyatUqFeueOnWqVKlSxa+M10BGGsg6sJ999ll55ZVXlFVu165dicES59aRkFq1asnYsWPlmGOOyWhC/mavATSQdWD/8ccfsmPHDjnyyCNTanzPnj0qxQ6g999/f78qXgOxaCDrwI5llP4hXgMhNeCBHVJh/vL80IAHdn6skx9lSA14YIdUmL88PzTgge24TpSG/vDDD/L111/L1q1bFb+cA3GTJk2kcuXKiaf89NNP8vbbb6uYfGkJY/v999/l119/VZz2gw8+2OnVUBp+++032b17t9SoUaNchVk9sC0QAMTwx+fOnavAk0qg3cI5h/Ny8803K9CXBgX3mWeekQULFqj3aoFv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" alt="">,遍历cells数组中的值,然后累加。

AtomicLong可以弃用了吗?

看上去LongAdder的性能全面超越了AtomicLong,而且阿里巴巴开发手册也提及到 推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观 锁的重试次数),但是我们真的就可以舍弃掉LongAdder了吗?

当然不是,我们需要看场景来使用,如果是并发不太高的系统,使用AtomicLong可能会更好一些,而且内存需求也会小一些。

我们看过sum()方法后可以知道LongAdder在统计的时候如果有并发更新,可能导致统计的数据有误差。

而在高并发统计计数的场景下,才更适合使用LongAdder

总结

LongAdder中最核心的思想就是利用空间来换时间,将热点value分散成一个Cell列表来承接并发的CAS,以此来提升性能。

LongAdder的原理及实现都很简单,但其设计的思想值得我们品味和学习。

YDzI1K.png

比AtomicLong更优秀的LongAdder确定不来了解一下吗?的更多相关文章

  1. 能让你成为更优秀程序员的10个C语言资源

    能让你成为更优秀程序员的10个C语言资源 本文由 伯乐在线 - archychu 翻译自 mycplus.欢迎加入 技术翻译小组.转载请参见文章末尾处的要求. 一些人觉得编程无聊,一些人觉得它很好玩. ...

  2. 比Kafka Mangaer更优秀的开源监控工具-Kafka Eagle

    比Kafka Mangaer更优秀的开源监控工具-Kafka Eagle 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 在Kafka的监控系统中有很多优秀的开源监控系统.比如Kaf ...

  3. 通过AI自学习,Google让Pixel 3的人像模式更优秀

    通过AI自学习,Google让Pixel 3的人像模式更优秀 Link: https://news.cnblogs.com/n/613720/ 虽然双摄手机已经在市场上普及,其所带来的人像模式.多倍变 ...

  4. 寻找性能更优秀的动态 Getter 和 Setter 方案

    反射获取 PropertyInfo 可以对对象的属性值进行读取或者写入,但是这样性能不好.所以,我们需要更快的方案. 方案说明 就是用表达式编译一个 Action<TObj,TValue> ...

  5. docker 换更优秀的 文件系统 比如 OverlayFS(centos7 overlay2)

    内容摘自:http://www.projectatomic.io/blog/2015/06/notes-on-fedora-centos-and-docker-storage-drivers/ doc ...

  6. 结合sklearn的可视化工具Yellowbrick:超参与行为的可视化带来更优秀的实现

    https://blog.csdn.net/qq_34739497/article/details/80508262 Yellowbrick 是一套名为「Visualizers」的视觉诊断工具,它扩展 ...

  7. Ember.js和Vue.js对比,哪个框架更优秀?

    本文由葡萄城技术团队于博客园翻译并首发 转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. JavaScript最初是为Web应用程序创建的.但是随着前端技术的 ...

  8. 一个比CBitmap更优秀的类 -- CImage类

    Visual C++的CBitmap类的功能是比较弱的,它只能显示出在资源中的图标.位图.光标以及图元文件的内容,而不像VB中的Image控件可以显示出绝大多数的外部图像文件(BMP.GIF.JPEG ...

  9. SkyWalking配上告警更优秀

    前言 对于监控系统来说,不可能让人一直盯着监控看板,而更多的是以自动提醒的方式,比如邮件.短信或微信推送等,当达到或超出预设的告警指标时,就自动发送消息提醒,下面就来说说如何配置SkyWalking的 ...

随机推荐

  1. JavaScript函数作用域和声明提前(3.10.1 page.57)

    <h4>3.函数作用域和声明提前</h4> <p> <!--<script type="text/javascript">-- ...

  2. SpringBoot集成Shiro实现权限控制

    Shiro简介 Apache Shiro是一个功能强大且易于使用的Java安全框架,用于执行身份验证,授权,加密和会话管理.使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序-从最小的移 ...

  3. file_put_contens小trick

    file_put_contents tricks 0x01 trick1 来自于P神的实例: <?php $text = $_GET['text']; if(preg_match('[<& ...

  4. vue原生表格怎样实现动态列及表格数据下载

    最近项目经常用到带有合并效果以及动态列的表格,而翻阅iview和element-ui官网没有找到合适的(也有可能自己的水平有限,不会改写),所以只好自己用原生表格写了一个,具体效果如下: 这个表格右侧 ...

  5. tensorflow1.0 构建神经网络做非线性归回

    """ Please note, this code is only for python 3+. If you are using python 2+, please ...

  6. jdbc-手写Java方法连接数据库

    一.关键四元素   ①    com.mysql.jdbc.Driver      mysql数据库连接jar包.   获取途径: 链接:https://pan.baidu.com/s/1SFcjuu ...

  7. XSS语义分析的阶段性总结(二)

    本文首发于“合天智汇”微信公众号,作者:Kale 前言 上次分享了javascript语义分析,并且简单介绍了新型xss扫描器的一些想法,如何在不进行大量fuzz的情况下又能准确的检测出xss漏洞,这 ...

  8. 微信小程序 POST传值跳坑

    来源:https://www.cnblogs.com/ordinaryk/p/8430462.html 加这个就行了: header : { 'content-type': 'application/ ...

  9. 《Cisco防火墙》一2.4 总结

    本节书摘来自异步社区<Cisco防火墙>一书中的第2章,第2.4节,作者 [巴西]Alexandre M.S.P. Moraes,更多章节内容可以访问云栖社区"异步社区" ...

  10. [20170616]recover copy of datafile 6.txt

    [20170616]no copy of datafile 6 found to recover.txt --//最近几天一直被这个问题纠缠,我虽然不知道问题在哪来,还是找到简单的解决方法,做1个记录 ...