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的問題。