LongAdder是一个线程安全的记数器,相比AtomicLong在高并发下有更好的性能。
LongAdder关键的方法是add。
add
add方法首先判断当前add操作是否符合cell是空、base执行CAS成功,通常情况下,都会对base执行相加,在并发比较高的情况下,CAS可能失败,就会进入里面代码块。
里面代码块判断如果满足4个条件任意1个,就执行下面的longAccumulate方法
- as是空,无法加上值,需要进入longAccumulate初始化
- as长度小于等于0,无法加上值,需要进入longAccumulate初始化
- 本次操作对应的槽是空,无法加上值,需要进入longAccumulate初始化
- 对槽指行CAS操作失败,需要进入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);
}
}longAccumulate执行的操作分别是
- 判断getProbe返回结果是否是0,如果是0,则说明ThreadLocalRandom还没有初始化,这时会进行初始化,并调用getProbe获取probe值
- 如果有cell,判断本次操作对应的槽是否是空,如果是空,判断cellsBusy是否是0,如果是,说明当前没有线程在操作cell,可以执行操作,这里用到了DCL,如果DCL成功,就进入里面代码块将当前槽增加1个cell,如果执行成功,要添加的值就被加上了,Cell对象入参;wasUncontended这个分支没看懂是在干嘛;然后第3个分支执行CAS操作,这里如果有fn,函数接口的实现,就会用函数接口的实现计算值,否则直接相加计算值;第4个分支判断槽位数是否大于CPU数、或者cell数组是否修改,如果满足1个条件,则说明不允许扩容;第5个分支判断是否允许扩容,如果不允许,改为允许;第6个分支判断cellsBusy是否等于0,表示是否有别的线程在操作,如果没有,则进行扩容
- 执行到for循环第2个大分支,表示cell是空,初始化下cell
- 前2个大分支都执行失败,再试下base执行CAS操作
- 如果都失败,自旋重试一下
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
int h;
if ((h = getProbe()) == 0) {//判断当前线程的probe值是否被初始化
ThreadLocalRandom.current(); // force initialization
h = getProbe();
wasUncontended = true;
}
boolean collide = false; // True if last slot nonempty
for (;;) {
Cell[] as; Cell a; int n; long v;
if ((as = cells) != null && (n = as.length) > 0) {//判断是否有cell
if ((a = as[(n - 1) & h]) == null) {//判断本次操作对应的槽是否是空
if (cellsBusy == 0) { // 第1次check
Cell r = new Cell(x);
if (cellsBusy == 0 && casCellsBusy()) {//加锁
boolean created = false;
try {
Cell[] rs; int m, j;
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {//第2次check
rs[j] = r;
created = true;
}
} finally {
cellsBusy = 0;
}
if (created)
break;
continue;//没有重新生成随机数
}
}
collide = false;
}
else if (!wasUncontended)
wasUncontended = true;
else if (a.cas(v = a.value, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))//如果有函数接口,用函数接口计算值,否则之间相加计算值
break;
else if (n >= NCPU || cells != as)//判断槽位数是否大于CPU数,或者槽位已经扩容,如果大于等于或者已经扩容,不允许扩容
collide = false; // At max size or stale
else if (!collide)//如果不允许扩容,改成允许扩容
collide = true;
else if (cellsBusy == 0 && casCellsBusy()) {//如果没有别的线程在操作,就执行扩容操作
try {
if (cells == as) {
Cell[] rs = new Cell[n << 1];
for (int i = 0; i < n; ++i)
rs[i] = as[i];
cells = rs;
}
} finally {
cellsBusy = 0;
}
collide = false;
continue;//没有重新生成随机数,因为扩容了,要做一次尝试
}
h = advanceProbe(h);//获取新的随机数
}
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {//cell是空,生成1个cell数组,添加要加的值
boolean init = false;
try { // Initialize table
if (cells == as) {
Cell[] rs = new Cell[2];
rs[h & 1] = new Cell(x);
cells = rs;
init = true;
}
} finally {
cellsBusy = 0;
}
if (init)
break;
}
else if (casBase(v = base, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))//上面都执行失败,重新试一下base变量的CAS操作
break;
}
}Cell
上面一直用到Cell是Striped64内部类。
Contended表示这个对象会被填充值,防止因为缓存行导致的伪共享影响性能。
valueOffset是Cell的变量value在类里的位置,因为CAS是方法操作,java方法原生类型是传值,对象是传引用,所以不能直接传入value进去,需要用这种方式修改变量的值,类似c的指针,或者底层就是用c的指针实现的。
@sun.misc.Contended static final class Cell {
volatile long value;
Cell(long x) { value = x; }
final boolean cas(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
}
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long valueOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> ak = Cell.class;
valueOffset = UNSAFE.objectFieldOffset
(ak.getDeclaredField("value"));
} catch (Exception e) {
throw new Error(e);
}
}
}sum
最后看一下sum方法,获取总数。
不出意外,是base的值加上所有cell的值。
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;
}