本文作者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 原理
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 原理