LongAdder和AtomicLong類似是用于多線程下來保證數據更新的原子性,AtomicLong主要是依賴CAS操作來保證原子性的,其方法本質是在循環中一直嘗試CAS,直到成功時才退出循環,所以在線程競爭激烈的場景往往性能不是很好(盡管已經比使用悲觀鎖好的多);
LongAdder采用的是類似分治的思想,再遇到多個線程同時對數據進行更新時,會將數據分為多份更細粒度的子單位再更新,從而達到減少線程競爭的目的,額外的消耗是需要更多的空間;在ConcurrentHashMap
中也有它的身影,
源碼實現
Striped64
是一個抽象類,里面的實現是LongAdder
操作方法的基礎;該類里面維護了一個表只允許原子性操作并且是懶加載的,大小為2的冪次方;
@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);
}
}
}
AtomicLong
類的變體,只支持原始訪問和CAS操作,這個就是LongAdder
中細粒度的單位;
/** cpu的數目,決定著細分cell的數量 */
static final int NCPU = Runtime.getRuntime().availableProcessors();
/** cells表,當非空時size是2的冪 */
transient volatile Cell[] cells;
/** 基本值,主要在沒有競爭時使用,通過CAS更新 */
transient volatile long base;
/** 自旋鎖,通過CAS鎖定 */
transient volatile int cellsBusy;
/** 可以看到更新BASE時是用cas操作 */
final boolean casBase(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
}
/** 從0到1的情況表示獲取鎖 把cellsBusy置0表示釋放鎖*/
final boolean casCellsBusy() {
return UNSAFE.compareAndSwapInt(this, CELLSBUSY, 0, 1);
}
base屬性就是沒有線程沖突時累加的數值,在遇到線程競爭時才進行分治處理;
final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) {
int h;
if ((h = getProbe()) == 0) {
// 當h值為0時表示未初始化
ThreadLocalRandom.current(); // 強制初始化
h = getProbe();
wasUncontended = true;
}
//如果最后一個槽非空,則為真,也用于控制擴容,false重試。
boolean collide = false;
for (;;) { //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) { //判斷鎖是否被使用
// 鎖未被使用,樂觀地創建并初始化cell。
Cell r = new Cell(x); // 樂觀地創建
if (cellsBusy == 0 && casCellsBusy()) {
// 鎖仍然是空閑的、且成功獲取到鎖
boolean created = false;
try {
// 在持有鎖時再次檢查槽是否空閑
Cell[] rs; int m, j;
if ((rs = cells) != null && //如果cells不為空
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
// 所映射的槽仍為空
rs[j] = r;// 關聯 cell 到槽
created = true;
}
} finally {
cellsBusy = 0;// 釋放鎖
}
if (created)
break;// 成功創建cell并關聯到槽,退出循環
continue; //走到這表示上面獲取到鎖時槽被占用了 需要重新循環申請鎖
}
}
collide = false;// 鎖被占用了,重試
}
// 槽被占用了
else if (!wasUncontended) // 已知CAS失敗
wasUncontended = true; // 在重散列后繼續,在當前槽的cell上嘗試更新,重裝散列表示重新刷新h值
else if (a.cas(v = a.value, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break;;
// 表達到上限后(最大為CPU核數)就不會再嘗試下面if的擴容了,只會重散列,嘗試其他槽
else if (n >= NCPU || cells != as)
collide = false;
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()) {
//未初始化表,在獲取鎖成功后初始化表
boolean init = false;
try {
if (cells == as) {
Cell[] rs = new Cell[2]; //初始化大小為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))))
//表未被初始化,可能正在被初始化,
//直接嘗試cas設置base值,如果本次cas成功則退出循環,否則重新判斷
break;
}
}
上面這段代碼就是LongAdder
類處理多線程沖突下的分治操作,總體而言操作流程如下;
- 發生線程競爭,判斷是否初始化過Table;如未初始化,會創建一個容量為2的Table,并且將value插入其中一個插槽;
- 第二次發生線程競爭時,會先根據
ThreadLocalRandom
的探針計算哈希值來尋找插槽,如果插槽為空,則插入插槽,如果插槽不為空,會嘗試通過CAS更新該插槽的值; - 如果插槽不為空且CAS更新失敗,則會嘗試擴建Table,最多擴張到大于或等于最接近CPU核數的2的冪次方;
通過ThreadLocalRandom
的探針字段來用于每個線程的哈希碼,為0意味著未初始化,發生線程沖突時,如果Table容量不能再擴大,且CAS操作失敗,則會進行雙重哈希,使用輔助哈希Marsaglia XorShift
嘗試查找空閑插槽;
//Marsaglia XorShif隨機數算法
static final int advanceProbe(int probe) {
probe ^= probe << 13; // xorshift
probe ^= probe >>> 17;
probe ^= probe << 5;
UNSAFE.putInt(Thread.currentThread(), PROBE, probe);
return probe;
}
回到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)) {
// cells 不為空 或更新base的cas失敗,也即出現了競爭。
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 || // as 為空表示cells未被初始化 || cells的長度為0
(a = as[getProbe() & m]) == null || // as[i]上的值為null,表示該位置沒有被占用
!(uncontended = a.cas(v = a.value, v + x))) //cas 成功,將as[i]的值替換成 oldvalue + x
// 如果所映射的槽不為空,且成功更新則返回,否則進入復雜處理流程。
longAccumulate(x, null, uncontended);
}
}
可以看出在沒有線程沖突時還是會先通過cas更新base值,極力避免進到Striped64
的復雜處理流程;
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聚合操作就是將base值和Table中所有Cell值相加得出,下圖可能有點不準確,但是思想是類似的;