并发场景下的number操作,都会选用java.util.concurrent.atomic包下的数据结构,比如典型的计数场景,而AtomicLong是其中一个,但是jdk1.8后Doug Lea大神又添加LongAdder和DoubleAdder,在大并发场景下性能会远高于AtomicLong,看下源码了解下原理。
首先看下AtomicLong是怎么保证原子操作,以add操作,拿其中 addAndGet 方法:
public final long addAndGet(long delta) {
return unsafe.getAndAddLong(this, valueOffset, delta) + delta;
}
public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
//取出旧的值用于cas操作
var6 = this.getLongVolatile(var1, var2);
//用CAS来保证原子性,如果操作失败就循环,var6+var4是新值
} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
return var6;
}
可以看出是通过底层cas操作保证原子操作,这里看下这个valueOffSet
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicLong.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
可以看出这个valueOffSet实际是当前AtomicLong实例的value属性在内存中的偏移量,看到这里就知道了通过CAS原子更新value的值。
AtomicLong实际是多个线程竞争修改这个value属性的机会。那优化锁竞争的一个思路就是将锁的粒度细化,比如之前concurrentHashMap的分段锁也是这个优化思路,那AtomicLong怎么将锁优化而又不改变最终的计算结果呢…
比如我需要将100 加个10 再加20,那么有下面这两种操作步骤:
- 第一种:先将100+10 = 110,然后再110+20 得到最终结果 130。
- 第二种:将100拆成2个50,在第一个50上面加10得到60,在最后一个50上加20得到70,最后将第一操作结果 60 和 第二次的操作结果70相加,得到最终的130。
LongAdder就是第二种操作思路,将原AtomicLong里的value拆成多个value,这样在并发情况下,就将多个线程竞争修改【一个】value属性的机会 --> 多个线程竞争修改【多个】value属性的机会,这样就相当于将性能翻倍。思路了解了,下面来看下LongAdder的代码:
以LongAdder的add方法为例:
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
if ((as = cells) != null || !casBase(b = base, b + x)) {//先进行一次CAS操作,如果操作不成功说明有竞争,则进入分段操作的逻辑
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);
}
}
这里的一个重要属性cells就是将value进行拆分的集合,比如将1000 拆成 200、400、400 那么这个cells就是一个长度为3的数组。 下面看下 longAccumulate方法逻辑:
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
int h;
if ((h = getProbe()) == 0) {
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) {
if ((a = as[(n - 1) & h]) == null) {
if (cellsBusy == 0) { // Try to attach new Cell
Cell r = new Cell(x); // Optimistically create
if (cellsBusy == 0 && casCellsBusy()) {
boolean created = false;
try { // Recheck under lock
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
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) { // Expand table unless stale
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
}
h = advanceProbe(h);
}
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
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))))
break; // Fall back on using base
}
}
主要看65-66行,这 个是cells初始化的地方,初始是2个长度,即将value默认是拆成2段,然后看下47-51这个是扩容的地方,扩容的时机是cellsBusy==0,从字面意思理解应该是整个cells的子元素都被线程占用了,那么就开始扩容到原来的两倍,看到这里LongAdder优化的思路及一些细节基本就清楚了。