LongAdder是一个原子性递增或者递减类,与AtomicLong的不同之处在于,LongAdder能够解决高并发情况下多线程同时竞争同意资源,而带来性能降低的问题。主要是通过将一个变量拆分为多个Cell,从而将单个变量的并发数分散到多个Cell上。关于LongAdder实现的几个分体如下:
(1)LongAdder 的结构是怎样的?
(2)当前线程应该出问Cell 数组里面的哪一个Cell元素?
(3)如何初始化Cell数组?
(4)Cell 数组如何扩容?
(5)线程访问分配的Cell元素有冲突后如何处理?
(6)如何保证线程操作被分配的Cell元素的原子性?
LongAdder的内部结构是LongAdder 继承Striped64实现Serializable,保证线程操作被分配的Cell元素的原子性可以看Cell的源码进行分析。
@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);
}
}
}
从源码中可以看到Cell类上的注解@sun.misc.Contended,用于防止伪共享的; volatile修饰的变量value,保证在多线程情况下value的内存可见性;cas方法保证当前线程更新是分配Cell元素中value的原子性。到这里问题1和6都已解决,下面就看看LongAdder类的递增或者递减方法的源码。
public void increment() {
this.add(1L);
}
public void decrement() {
this.add(-1L);
}
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
//(0)cells如果为null,则在运行casBase方法,在base上进行累加
if ((as = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 || //(1)判断cells数组是非未空或者元素数量为0
(a = as[getProbe() & m]) == null || // (2)通过as[getProbe() & m]确定当前线程使用哪个cell
!(uncontended = a.cas(v = a.value, v + x))) //(3)执行Cell类中的cas方法,更新value的值
longAccumulte(x, null, uncontended);//(4)
}
}
final boolean casBase(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
}
从源码可以看到递增和递减方法调用的都是同一个方法add,从(2)处可以回答第二个问题,当前线程如和获取cell的。代码中还能发现LongAdder中的add方法中 Cell[] as元素是通过as = cells初始化得到的,而这个cells是在Striped64类中的属性;如果if条件都不满足的请况下如何初始化cells的呢?下面从longAccumulte这个方法找寻答案。
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
int h;
if ((h = getProbe()) == 0) {
ThreadLocalRandom.current(); // 初始化,在线程选择cells中的哪个cell是会用到
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) { //cells不为空且有元素
if ((a = as[(n - 1) & h]) == null) { // 给当前线程分配cell失败
if (cellsBusy == 0) { // cellsBusy 标识位为0说明cells数组没有被初始化或者扩容
Cell r = new Cell(x); // 给当前线程创建一个新的cell
//再次判断cellsBusy标识位为0 且源自操作成功
if (cellsBusy == 0 && casCellsBusy()) {
boolean created = false;
try {
// 再次确认当前线程所在的cells数组中的位置没有cell,再将新建的cell放进相应的位置
Cell[] rs; int m, j;
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
rs[j] = r;
created = true;
}
} finally {
cellsBusy = 0;
}
if (created)
break;
continue; // Slot is now non-empty
}
}
collide = false;
}
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
//调用cell的cas方法,更新value值,fn为指定的计算逻辑,如果为空则使用默认的计算逻辑//v+x,如果不为空则使用指定的fn函数计算,从LongAdder的add方法上可以看到fn是null,所以在//LongAdder中使用的是默认的计算逻辑
else if (a.cas(v = a.value, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break;
else if (n >= NCPU || cells != as)
collide = false; // At max size or stale
else if (!collide)
collide = true;
else if (cellsBusy == 0 && casCellsBusy()) {
try {
if (cells == as) { //cells数组扩容为原来的两倍
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; // Retry with expanded table
}
//为了能够找到一个空闲的Cell ,重新计算hash值, xorshift 算法生成随机数,这样就可以解决分配cell的冲突问题
h = advanceProbe(h);
}
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
boolean init = false;
try { //初始化cells
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))))
break; // Fall back on using base
}
}
从上面的源码分析中可以解决问题3 4 5的问题。