Java LongAdder 原理

本文作者ycwu314,未经允许请勿转载Java LongAdder 原理 : https://ycwu314.github.io/p/java-longadder/

温馨提示:如果不是在这些地方看到这篇文章,那么你可能是爬虫文章的受害者:

https://ycwu314.github.io/

https://www.cnblogs.com/

https://blog.csdn.net/

未经授权的转载,不仅打击原创积极性,还获取不到最新的文章更新信息。

有了Striped64的基础,LongAdder就见多了。

相关文章:

  • Java Striped64 原理

Java LongAdder 原理

LongAdder add 源码分析

因为继承自Striped64,因此LongAdder使用了

transient volatile Cell[] cells;
transient volatile long base;
transient volatile int cellsBusy;

前情回顾:Striped64有3个核心变量,都是volatile修饰:

  • base:计数字段
  • cells:是一个Cell数组。使用lazy init方式。第一次访问时候初始化。
  • cellsBusy:用于自旋,表明cells数组正在初始化或者扩容。0表示无cells竞争,1表示有线程在操作cells。

LongAdder核心是add()

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

第一个if:

  • 如果cells不为空,则直接进入内层判断。(Striped64优先使用分段锁cells更新计数)
  • 否则,cells为空,此时正在初始化,则尝试CAS更新base字段。如果成功则退出,否则进入内层判断。

第一个if虽然简单,但是值得细细品味。

然后乐观假设没有竞争uncontended=true,进入第二个if。

第二个if:

(m = as.length - 1) < 0
(a = as[getProbe() & m]) == null
a.cas(v = a.value, v + x)

具体的Striped64.longAccumulate()就不在重复了。

LongAdder sum 源码分析

显然计数存在于base和cells数组。

真实的计数=base + sum (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;
}

因为这个方法没有加锁,不是并发安全的。

javadoc上就有温馨提示:

The returned value is NOT anatomic snapshot

LongAdder reset 源码分析

resetfangf重置base和cells。可以复用LongAdder对象。同样,这个方法也不是线程安全的。个人感觉用途不大。

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

LongAdder SerializationProxy

类图可以看到,LongAdder实现了Serializable接口。但是LongAdder本身没有数据变量,所有属性来自父类Striped64。

transient volatile Cell[] cells;
transient volatile long base;
transient volatile int cellsBusy;

Striped64中这几个变量都是non-public。不应该暴露这几个变量。同时,真正想要序列化的是真实的计数(当然只是snapshot)。因此LongAdder设计了内部类SerializationProxy,用于序列化和反序列化。

private static class SerializationProxy implements Serializable {
    private static final long serialVersionUID = 7249069246863182397L;
    /**
     * The current value returned by sum().
     * @serial
     */
    private final long value;
    SerializationProxy(LongAdder a) {
        value = a.sum();
    }

    private Object readResolve() {
        LongAdder a = new LongAdder();
        a.base = value;
        return a;
    }    
}

直接sum计算当前计数。

Serializable接口的类支持使用readObject提供自定义的反序列化方式。这里直接返回异常,强调要使用SerializationProxy操作。

private void readObject(java.io.ObjectInputStream s)
    throws java.io.InvalidObjectException {
    throw new java.io.InvalidObjectException("Proxy required");
}

LongAdder vs AtomicLong

AtomicLong是乐观锁设计,CAS更新计数value。在多读少写的情况下性能好。

但是写竞争激烈的情况,乐观锁的前提就不存在了,导致大量更新线程自旋等待。

LongAdder继承自Striped64,使用了分段锁的设计、缓冲行填充优化,并发度更大,尽可能分散竞争。

写并发不高的情况下,AtomicLong已经够用了。

题外话

原文 

https://ycwu314.github.io/p/java-longadder/

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Java LongAdder 原理

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址