【Java併發】淺析 AtomicLong & LongAdder

AtomicLong

/**
 * Atomically increments by one the current value.
 *
 * @return the updated value
 */
public final long incrementAndGet() {
    return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}

unsafe

public final long getAndAddLong(Object var1, long var2, long var4) {
    long var6;
    do {
        var6 = this.getLongVolatile(var1, var2);
    } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
    // 關注重點:if(var2 == var6)
    return var6;
}
  • var1 調用原方法 incrementAndGet 即自身的對象
  • var2 原對象當前(工做內存中的)值
  • var4 要加上去的值
  • var6 調用底層方法 getLongVolatile 得到當前(主內存中的)值,若是沒其餘線程修改即與 var2 相等

compareAndSwapLong

  • var2var6 爲何可能會不同?html

    • 在併發環境下,工做內存中的值 var2 與主內存中的值 var6 之間的可能不同(JMM)
// 獲取主內存裏的值
public native long getLongVolatile(Object var1, long var2);
// CAS 操做
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
native關鍵字說明其修飾的方法是一個原生態方法,方法對應的實現不是在當前文件,而是在用其餘語言(如 C 和 C++)實現的文件中。Java 語言自己不能對操做系統底層進行訪問和操做,可是能夠經過JNI接口調用其餘語言來實現對底層的訪問。

LongAdder

public void increment() {
    add(1L);
}

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); // <- 重點
    }
}

Cell

  • Cell 類,是一個普通的二元算術累積單元,它在 Striped64 裏面。Striped64 這個類使用分段的思想,來儘可能平攤併發壓力(相似1.7及之前版本的 ConcurrentHashMap.Segment)。
  • 最終依舊使用 compareAndSwapLong 來更新值。
/**
 * Padded variant of AtomicLong supporting only raw accesses plus CAS.
 *
 * JVM intrinsics note: It would be possible to use a release-only
 * form of CAS here, if it were provided.
 */
@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);
        }
    }
}

longAccumulate

  • Long 映射到 Cell[] 數組裏面,經過 Hash 等算法映射到其中一個數字進行計數,而最終的計數結果就是其求和累加。在低併發的時候,經過對 base 的直接更新,能夠很好地保證和 Atomic 性能的基本一致;而在高併發的時候,則將單點的更新壓力分散到各個節點上,提高了性能。

總結

  • AtomicLong 適用於序號生成,這種狀況下須要準確的、全局惟一的數值;但在高併發狀況下的計數操做,使用 AtomicLong 時會因線程競爭致使失敗白白循環一次;失敗次數越多,循環次數也越多。此時使用LongAdder 能更好地提高性能。
  • LongAdder 適用於高併發狀況下的計數操做,利用與 JDK1.7 ConcurrentHashMap 類似的原理,以空間換時間,提升了實際的計數效率。固然,線程競爭很低的狀況下進行計數,使用 AtomicLong 仍是更簡單更直接,而且效率稍微高一些。

注意:CAS 是 sun.misc.Unsafe 中提供的操做,只對 int、long、對象類型(引用或者指針)提供了這種操做,其餘類型都須要轉化爲這三種類型才能進行 CAS 操做。(例如 DoubleAdder 就是 LongAdder 的簡單改造,主要的變化就是用 Double.longBitsToDoubleDouble.doubleToRawLongBits 對底層的8字節數據進行 long <=> double 轉換,存儲的時候使用 long 型,計算的時候轉化爲 double 型。)java


參考資料算法

相關文章
相關標籤/搜索