咱們知道,AtomicLong的實現方式是內部有個value 變量,當多線程併發自增,自減時,均經過CAS 指令從機器指令級別操做保證併發的原子性。html
// setup to use Unsafe.compareAndSwapLong for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; /** * Records whether the underlying JVM supports lockless * compareAndSwap for longs. While the Unsafe.compareAndSwapLong * method works in either case, some constructions should be * handled at Java level to avoid locking user-visible locks. */ static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8(); /** * Returns whether underlying JVM supports lockless CompareAndSet * for longs. Called only once and cached in VM_SUPPORTS_LONG_CAS. */ private static native boolean VMSupportsCS8(); static { try { valueOffset = unsafe.objectFieldOffset (AtomicLong.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile long value; /** * Creates a new AtomicLong with the given initial value. * * @param initialValue the initial value */ public AtomicLong(long initialValue) { value = initialValue; } /** * Creates a new AtomicLong with initial value {@code 0}. */ public AtomicLong() { }
先看LongAdder的add()方法:shell
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是Striped64的一個內部類,顧名思義,Cell 表明了一個最小單元,這個單元有什麼用,稍候會說道。先看定義:數組
/** * 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); } } }
Cell內部有一個很是重要的value變量,而且提供了一個更新其值的cas()方法。多線程
回到add方法:併發
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); } }
這裏,我有個疑問,AtomicLong已經使用CAS指令,很是高效了(比起各類鎖),LongAdder若是仍是用CAS指令更新值,怎麼可能比AtomicLong高效了? 況且內部還這麼多判斷!!!less
這是我開始時最大的疑問,因此,我猜測,難道有比CAS指令更高效的方式出現了? 帶着這個疑問,繼續。dom
第一if 判斷,第一次調用的時候cells數組確定爲null,所以,進入casBase方法:ide
/** * CASes the base field. */ final boolean casBase(long cmp, long val) { return UNSAFE.compareAndSwapLong(this, BASE, cmp, val); }
原子更新base沒啥好說的,若是更新成功,本地調用開始返回,不然進入分支內部。高併發
何時會更新失敗? 沒錯,併發的時候,好戲開始了,AtomicLong的處理方式是死循環嘗試更新,直到成功才返回,而LongAdder則是進入這個分支。性能
分支內部,經過一個Threadlocal變量threadHashCode 獲取一個HashCode對象,該HashCode對象依然是Striped64類的內部類,看定義:
/** * Returns the probe value for the current thread. * Duplicated from ThreadLocalRandom because of packaging restrictions. */ static final int getProbe() { return UNSAFE.getInt(Thread.currentThread(), PROBE); }
有個code變量,保存了一個非0的隨機數隨機值。
回到add方法:
拿到該線程相關的HashCode對象後,獲取它的code變量,as[(n-1)&h] 這句話至關於對h取模,只不過比起取模,由於是 與 的運算因此效率更高。
計算出一個在Cells 數組中當先線程的HashCode對應的 索引位置,並將該位置的Cell 對象拿出來用CAS更新它的value值。
看到這裏我想應該有不少人明白爲何LongAdder會比AtomicLong更高效了,沒錯,惟一會制約AtomicLong高效的緣由是高併發,
高併發意味着CAS的失敗概率更高, 重試次數更多,越多線程重試,CAS失敗概率又越高,變成惡性循環,AtomicLong效率下降。
那怎麼解決?
LongAdder給了咱們一個很是容易想到的解決方案:減小併發,將單一value的更新壓力分擔到多個value中去,下降單個value的 「熱度」,分段更新!!!
這樣,線程數再多也會分擔到多個value上去更新,只須要增長value就能夠下降 value的 「熱度」 AtomicLong中的 惡性循環不就解決了嗎?
cells 就是這個 「段」 cell中的value 就是存放更新值的, 這樣,當我須要總數時,把cells 中的value都累加一下不就能夠了麼!!
固然,聰明之處遠遠不只僅這裏,在看看add方法中的代碼,casBase方法可不能夠不要,直接分段更新,上來就計算 索引位置,而後更新value?
答案是很差,不是不行,由於,casBase操做等價於AtomicLong中的CAS操做,要知道,LongAdder這樣的處理方式是有壞處的,分段操做必然帶來空間上的浪費,
能夠空間換時間,可是,能不換就不換,看空間時間都節約~! 因此,casBase操做保證了在低併發時,不會當即進入分支作分段更新操做,由於低併發時,
casBase操做基本都會成功,只有併發高到必定程度了,纔會進入分支,
因此,Doug Lea對該類的說明是: 低併發時LongAdder和AtomicLong性能差很少,高併發時LongAdder更高效!
1. base有沒有參與彙總?
base在調用intValue等方法的時候是會彙總
2. 若是cell被建立後,原來的casBase就不走了,會不會性能更差? base的順序可不能夠調換?
剛開始我想可不能夠調換add方法中的判斷順序,好比,先作casBase的判斷?
仔細思考後認爲仍是不調換可能更好,調換後每次都要CAS一下,在高併發時,失敗概率很是高,而且是惡性循環,比起一次判斷,
後者的開銷明顯小不少,尚未反作用(上一個問題,base變量在sum時base是會被統計的,並不會丟掉base的值)。所以,不調換可能會更好。
3. AtomicLong可不能夠廢掉?
雖然LongAdder在空間上佔用略大,可是,它的性能已經足以說明一切了,不管是從節約空的角度仍是執行效率上,AtomicLong基本沒有優點了
4. guava 、netty裏面都照搬了LongAdder的實現
出處: