Java并发编程之LongAdder类

153 阅读3分钟

这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战

AtomicLong通过CAS提供了非阻塞的原子性操作,但是使用AtomicLong时,在高并发下大量线程会同时间去竞争更新同一个原子变量,但是由于同时只有一个线程的CAS会操作成功,这就会造成了大量线程竞争失败后,会通过无限循环不断进行自旋尝试CAS操作,而这会白白浪费CPU资源。因此JDK8新增了一个原子性递增/减的类LongAdder来克服在高并发下使用AtomicLong的缺点。使用LongAdder时,是在内部维护多个cell变量,每个cell里面有一个初始值为0的long型变量,这样变相减少了争夺共享资源的并发量。另外,多个线程争夺同一个cell失败后,不会在同一个cell上一直自旋CAS重试,而是尝试在其他cell上进行自旋尝试,这增加了CAS重试的成功可能性。

image.png

LongAdder 维护了一个延迟初始化的原子性更新散组(默认情况下 Cell数组是null和一个基值变量 base ,由于 Cells 占用的内存是相对比较大的,所以一开始并不创建它,而是在需要时创建。也就是情性加载。 当一开始判断cell数组是null并且并发线程较少时,所有的累加操作都是对 base 变量进行的。保持 Cell 數组的大小为2的 N 次方,在初始化时 Cell 数组中的 Cell 元素个数为2,数组里面的变量实体是 Cell 类型。 Cell 类型是 AtomicLong 的一个改进,用来减少缓存的争用,也就是解决伪共早间题。

对于大多数孤立的多个原于操作进行字节填充是浪费的,因为原子性操作都是无规律地分散在内存中的(也就是说多个原子性变量的内存地址是不连续的),多个原子变量被放入同一个缓存行的可能性根小。但是原子性数组元素的内存地址是连续的,所以数组内的多个元素能经常共享缓存行,因此这里使用@sun.misc.Contended 注解对 Cell 类进行字节填充。这防止了数组中多个元素共享一个缓存行,在性能上是一个提升。

LongAdder代码分析

LongAdder类继承自Striped64类,在Striped64类中维护着三个变量。LongAdder的真实值其实事base的值与cell中的所有的value值累加的结果。base就是基础值,默认为0,cellsBusy用来实现自旋锁,状态值只有0和1,是CAS来操作该变量来保证同时只有一个线程进行扩容或者初始化操作。

  • long sum() :

返回当前你的值,内部操作是累加所有的Cell内部的value值后在累加base。由于在累加的时候没有Cell数组加锁,累加期间cell的值可能会有变化,所以返回的最终值不一定准确。

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;
}
  • void reset():

重置操作,将base重置为0,如果cell不为空的话,将cell中的元素值置为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;
        }
    }
}
  • long sumThenReset(): 在使用sum累加对应的Cell值后,把当前的Cell重置为0,base重置为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;
}
  • void add(long x):
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);
    }
}