摘要:最終依舊使用來更新值。此時使用能更好地提升性能。適用于高并發情況下的計數操作,利用與相似的原理,以空間換時間,提高了實際的計數效率。
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
var2 與 var6 為什么可能會不一樣?
在并發環境下,工作內存中的值 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.longBitsToDouble 和 Double.doubleToRawLongBits 對底層的8字節數據進行 long <=> double 轉換,存儲的時候使用 long 型,計算的時候轉化為 double 型。)
參考資料
談談ConcurrentHashMap1.7和1.8的不同實現
jdk1.8 LongAdder源碼學習
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69085.html
摘要:在并發量較低的環境下,線程沖突的概率比較小,自旋的次數不會很多。比如有三個,每個線程對增加。的核心方法還是通過例子來看假設現在有一個對象,四個線程同時對進行累加操作。 showImg(https://segmentfault.com/img/remote/1460000016012084); 本文首發于一世流云的專欄:https://segmentfault.com/blog... ...
摘要:失敗重試自旋比如說,我上面用了個線程,對值進行加。我們都知道如果在線程安全的情況下,這個值最終的結果一定是為的。那就意味著每個線程都會對這個值實質地進行加。 前言 只有光頭才能變強 之前已經寫過多線程相關的文章了,有興趣的同學可以去了解一下: https://github.com/ZhongFuCheng3y/3y/blob/master/src/thread.md showImg(h...
摘要:原子類的作用多線程操作,性能開銷太大并不是原子操作。每次比較的是兩個對象性能比要好使用時,在高并發下大量線程會同時去競爭更新同一個原子變量,但是由于同時只有一個線程的會成功,所以其他線程會不斷嘗試自旋嘗試操作,這會浪費不少的資源。 AtomicInteger 原子類的作用 多線程操作,Synchronized 性能開銷太大count++并不是原子操作。因為count++需要經過讀取-...
摘要:在有些情況下,原子操作可以在不使用關鍵字和鎖的情況下解決多線程安全問題。但其內部的結果不是一個單一的值這個類的內部維護了一組變量來減少多線程的爭用。當來自多線程的更新比讀取更頻繁時這個類往往優于其他的原子類。 原文地址: Java 8 Concurrency Tutorial: Atomic Variables and ConcurrentMap AtomicInteger java...
閱讀 3207·2021-11-10 11:36
閱讀 3153·2021-11-02 14:39
閱讀 1732·2021-09-26 10:11
閱讀 4963·2021-09-22 15:57
閱讀 1692·2021-09-09 11:36
閱讀 2055·2019-08-30 12:56
閱讀 3493·2019-08-30 11:17
閱讀 1707·2019-08-29 17:17