众所周知,LongAdder通过分段更新的方式,保证了在高并发下依然性能很好。Long值在其内部是分段保存的,只有在真正获取Longadder的值的时候才会去计算。
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;
}
在我的使用过程中,经常会碰到的场景是,对LongAdder写操作的并发很高,但是对值的读并发同样很高,这时候,每次获取值都要调用sum()去实时计算就会显得比较累赘,影响性能。所以在这个实现已经很完美的类上,做了一个小小的优化,思路很简单,缓存上次计算的long值,除非有写操作,否则直接读取缓存的值即可。
具体见代码&注释
public class CacheLongAdder extends LongAdder implements Serializable {
private static final long serialVersionUID = 7249069246863181097L;
/**
* 上次写时间
*/
private volatile Long lastModify = null;
/**
* 上次读时间
*/
private volatile Long lastRead = null;
/**
* 缓存的long值
*/
private Long cacheValue = null;
public CacheLongAdder() {
this.lastModify = System.currentTimeMillis();
}
@Override
public void add(long x) {
super.add(x);
// 不需要对此值的变更进行并发控制,判断缓存值失效只需要比较大小即可
lastModify = System.currentTimeMillis();
}
@Override
public long sum() {
if (cacheValue == null || lastRead == null || lastRead < lastModify) {
cacheValue = super.sum();
// 在没有写操作的情况下,直接使用缓存long值即可
// 只有在缓存失效,即发生了新的写操作的情况下,才需要更新lastRead的时间,避免下次读操作的判断错误
// 对并发情况下可能存在的误差是可以容忍度的,因为LongAdder本身的sum方法就不是原子性的
lastRead = System.currentTimeMillis();
}
return cacheValue;
}
}