项目场景:
使用LongAdder本地保存数据,定时的去拉取本地缓存中的数据更新到数据库。
问题描述:
因为Mentor要求我保存数据的时候要考虑高并发和性能问题,所以我也就想到了使用LongAdder。LongAdder在添加的时候性能会比AtomicInteger更好,原因在于其longAccumulate()方法和getAndAddInt()方法的区别,有兴趣的可以自己去看看源码。不得不说每次看完大神Doug Lea写的代码,就两个字牛逼。虽然LongAdder的性能比AtomicInteger更好,但是LongAdder获取的值不是准确值。这个问题我也咨询了一下Mentor,Mentor表示肯定是不能丢数据,可以用一定的技术解决这个问题,那不用说了我必须要做一个有技术的人。
public void getSum() {
LongAdder longAdder = new LongAdder();
longAdder.sum();
}
原因分析:
这里可以先看下LongAdder的sum()方法,方法里面是先获取到base的值,再通过遍历cells数组,最后累加base和数组的值返回。
public long sum() {
Cell[] var1 = this.cells;
long var3 = this.base;
if (var1 != null) {
//遍历cells数组
for(int var5 = 0; var5 < var1.length; ++var5) {
Cell var2;
if ((var2 = var1[var5]) != null) {
var3 += var2.value;//累加
}
}
}
return var3;
}
在并发环境下这个方法肯定会出现获取的值小于添加的值,例如:一个线程正在调用sum()方法获取值,另外一个线程在调用add()方法添加值,那添加的值可能就落在你之前已经遍历过的数组下标或者是base中,最终累加得到的sum值肯定少加了一些值是不正确的。
解决方案:
这个问题网上搜了一下,但是没有找到解决办法,最后想到了一个比较简单的办法。
大概的代码流程:
private static LongAdder longAdder1 = new LongAdder();
private static LongAdder longAdder2 = new LongAdder();
private static LongAdder data = longAdder1;//默认longAdder1
private static boolean flag = true;//开关
public void add() {
data.increment();
}
public long getSum() {
long sum = 0;
if (flag) {
data = longAdder2;
sum = longAdder1.sum();
flag = false;
} else {
data = longAdder1;
sum = longAdder2.sum();
flag = true;
}
return sum;
}
流程图:
这个方法其实就是类似于一个开关按钮,一开始默认在longAdder1进行添加,当我要获取值的时候,就按下开关让它在另外一边longAdder2进行添加,所以就能保证在longAdd1上获取的是正确值也不会影响添加,代码逻辑也比较简单,缺点是空间开销更大。暂时只能想到这个办法解决问题,有时间再想想还能不能优化吧。