纯净、安全、绿色的下载网站

首页|软件分类|下载排行|最新软件|IT学院

当前位置:首页IT学院IT技术

LongAdder源码解析 一篇带你解析入门LongAdder源码

兴趣使然の草帽路飞   2021-06-10 我要评论
想了解一篇带你解析入门LongAdder源码的相关内容吗兴趣使然の草帽路飞在本文为您仔细讲解LongAdder源码解析的相关知识和一些Code实例欢迎阅读和指正我们先划重点:解析LongAdder,LongAdder入门下面大家一起来学习吧

1、LongAdder由来

LongAdder类是JDK1.8新增的一个原子性操作类AtomicLong通过CAS算法提供了非阻塞的原子性操作相比受用阻塞算法的同步器来说性能已经很好了但是JDK开发组并不满足于此因为经常搞并发的请求下AtomicLong的性能是不能让人接受的

如下AtomicLong 的incrementAndGet的代码虽然AtomicLong使用CAS算法但是CAS失败后还是通过无限循环的自旋锁不多的尝试这就是高并发下CAS性能低下的原因所在源码如下:

public final long incrementAndGet() {
        for (;;) {
            long current = get();
            long next = current + 1;
            if (compareAndSet(current, next))
                return next;
        }
    }

高并发下N多线程同时去操作一个变量会造成大量线程CAS失败然后处于自旋状态导致严重浪费CPU资源降低了并发性

2、LongAdder与AtomicLong的简单介绍

我们知道volatile关键字是轻量级锁可以解决多线程内存不可见问题对于一写多读可以解决变量同步问题但是如果是多写volatile无法解决线程安全问题的例如count++操作就应该使用如下方式: AtomicInteger count = new AtomicInteger();count.addAndGet(1);而如果是JDK8及以上推荐使用LongAdder对象替代因为它的性能比AtomicLong 更好(减少乐观锁的重试次数)

LongAdder其他应用场景:

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

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

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

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

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

3、AtomicLong

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

3.1 AtomicLong实现原理

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

public class AtomicLong extends Number implements java.io.Serializable {
    // + 1
    public final long incrementAndGet() {
        return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
    }
    // - 1
    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值的原子性更新

3.2 AtomicLong瓶颈分析

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

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

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

4、LongAdder

4.1 LongAdder和AtomicLong性能测试

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

/**
 * Atomic和LongAdder耗时测试
 */
 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());
    }
}

执行结果:

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

4.2 LongAdder为什么这么快

先看下LongAdder的操作原理图:

既然说到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进行累加的操作就执行完了流程图如下:

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

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

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

@sun.misc.Contended static final class Cell {
}

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

解决伪共享的方法一般都是使用直接填充我们只需要保证不同线程的变量存在于不同的 CacheLine 即可使用多余的字节来填充可以做点这一点这样就不会出现伪共享问题例如在Disruptor队列的设计中就有类似设计

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

框中的翻译如下:

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

3、惰性求值

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

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

4.3 LongAdder实现原理

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

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

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

4.4 ongAdder源码剖析

前面已经用图分析了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数组中的元素去写值

条件二:(a = as[getProbe() & m]) == null

getProbe()获取当前线程的hash值m表示cells长度-1cells长度是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()方法

接着往下看核心的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分支很多除此看肯定会很头疼这里一点点分析然后结合画图一步步了解其中实现原理

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

  • cells数组没有初始化
  • cells数组已经初始化但是当前线程对应的cell数据为空
  • 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()为00与任何数取模都是0会固定到数组第一个位置所以这里做了优化使用ThreadLocalRandom为当前线程重新计算一个hash值最后设置wasUncontended = true这里含义是重新计算了当前线程的hash后认为此次不算是一次竞争hash值被重置就好比一个全新的线程一样所以设置了竞争状态为true

接着执行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]表示数组的长度为2rs[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.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.4:

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

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

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

接着看CASE1.5:

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

如果扩容意向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];

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

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

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

4.5 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 +遍历cells数组中的值然后累加

4.6 AtomicLong可以弃用了吗?

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

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

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

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

5、总结

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

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

也希望大家多多关注的其他内容!


相关文章

猜您喜欢

  • PyQt5 图片显示滚动 PyQt5实现多张图片显示并滚动

    想了解PyQt5实现多张图片显示并滚动的相关内容吗ttphoon在本文为您仔细讲解PyQt5 图片显示滚动的相关知识和一些Code实例欢迎阅读和指正我们先划重点:PyQt5,图片显示滚动,PyQt5,图片显示下面大家一起来学习吧..
  • Spring容器 详解Spring容器的使用流程

    想了解详解Spring容器的使用流程的相关内容吗沉默着忍受在本文为您仔细讲解Spring容器的相关知识和一些Code实例欢迎阅读和指正我们先划重点:Spring容器的使用,Spring容器下面大家一起来学习吧..

网友评论

Copyright 2020 www.fresh-weather.com 【世纪下载站】 版权所有 软件发布

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 点此查看联系方式