Adder
LongAdder 使用一个变量和一个动态增长的数组来共同保存一个 long 型的 sum 值,初始值为 0。它继承了 Striped64,在高并发时将线程分发到数组的不同元素做更新,以此来降低总体的竞争。因此相比于 AtomicLong,在高并发下 LongAdder 会更高的吞吐。不过由于将一个数值分散到多个地方,从 LongAdder 获取的值可能没法“立即”可见(累加时可能被其它线程修改了)。
LongAdder 的主体逻辑重用了 Striped64 的 accumulate 方法,需要先了解下Striped64的实现,这里只看看上层封装的内容。
add
增加某个值时,会尝试先用 casBase 来更新 base 的值;否则会尝试用 getProbe
获取线程的哈希值,找到对应的数组元素(as[getProbe() & m]),并尝试更新元素的值;如果都失败再调用 Striped64 的 longAccumulate 方法更新值。
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 条件判断中会调用 CAS 操作来做修改,方法有副作用。这种方式在日常的编码中不提倡。
sum
sum 方法很直接,累加 base 与 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;
}
这里的重点是 sum 方法不是原子的,在 sum 过程中,如果有其它线程在修改值,则
sum 的结果可能是“老”的。
DoubleAdder
DoubleAdder 和 LongAdder 几乎一样,只是存储是用 long 型,所以在存储/读取
double 时需要使用 Double.doubleToRawLongBits 和 Double.longBitsToDouble
做转换。