AtomicLong原始碼淺析(基於jdk1.8.0_231)
阿新 • • 發佈:2020-08-13
AtomicLong 簡介
- 在32位作業系統中,64位的long 和 double 變數由於會被JVM當作兩個分離的32位來進行操作,所以不具有原子性。而AtomicLong能讓long的加1,減1操作,設定新值等操作在多執行緒中保持原子性;
- AtomicLong 雖然繼承了Number 但不是 Long的替代品,即不要濫用;
- AtomicLong的原子性操作並不由加鎖支援的,而是有CompareAndSwap(簡稱 CAS )支援的原子性;
AtomicLong UML
AtomicLong 關鍵技術分析
CAS關鍵技術(CompareAndSwap 又稱作比較交換)
我們對一個數字A做修改前(注意是前,修改的動作還沒發生,我們修改前需要做些準備工作),首先會得到 儲存 A 的地址,即為addrA, A的值,即為addA(A)(即為當前addA 中記錄的A的值)。得到addA addA(A)就是修改前的準備工作,此期間別的執行緒也可以讀寫A哦。
當對A開始修改了,流程如下:
第一步:去addA中去看一下此刻此地址的A值的,即為\(V_{A}\)
第二步:比較 \(V_{A}\) 和 addA(A) 是否相等,若相等,則執行更新,如將 A+B的值寫入addA,結束。(這步操作是原子性的,是由作業系統的指令支援的,任何一條單獨的指令都是原子的,要不執行,要不不執行,設想若指令不是原子性的,那計算機組成中的多級流水線等一系列優化的大廈會轟然倒塌,若是一系列指令完成的,必然要在這些指令的執行“加鎖”,如鎖住匯流排,獨佔CPU,或者讓現線上程棧(java虛擬機器棧)中修改好快取,修改快取期間,其他執行緒不能修改該快取對應的主存位置,快取寫好後立即強制寫回主存,放開主存鎖定,讓其他所有執行緒可見,這一塊都是計算機組成的一些知識,僅做拋磚引玉)
第三步: 若 \(V_{A}\)
AtomicLong 原始碼解讀
package java.util.concurrent.atomic; import java.util.function.LongUnaryOperator; import java.util.function.LongBinaryOperator; import sun.misc.Unsafe; /** * @since 1.5 * @author Doug Lea */ public class AtomicLong extends Number implements java.io.Serializable { private static final long serialVersionUID = 1927816293512124184L; // 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() { } /** * Gets the current value. * * @return the current value */ public final long get() { return value; } /** * Sets to the given value. * * @param newValue the new value */ public final void set(long newValue) { value = newValue; } /** * Eventually sets to the given value. * * @param newValue the new value * @since 1.6 */ public final void lazySet(long newValue) { unsafe.putOrderedLong(this, valueOffset, newValue); } /** * Atomically sets to the given value and returns the old value. * * @param newValue the new value * @return the previous value */ public final long getAndSet(long newValue) { return unsafe.getAndSetLong(this, valueOffset, newValue); } /** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * * @param expect the expected value * @param update the new value * @return {@code true} if successful. False return indicates that * the actual value was not equal to the expected value. */ public final boolean compareAndSet(long expect, long update) { return unsafe.compareAndSwapLong(this, valueOffset, expect, update); } /** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * * <p><a href="package-summary.html#weakCompareAndSet">May fail * spuriously and does not provide ordering guarantees</a>, so is * only rarely an appropriate alternative to {@code compareAndSet}. * * @param expect the expected value * @param update the new value * @return {@code true} if successful */ public final boolean weakCompareAndSet(long expect, long update) { return unsafe.compareAndSwapLong(this, valueOffset, expect, update); } /** * Atomically increments by one the current value. * * @return the previous value */ public final long getAndIncrement() { return unsafe.getAndAddLong(this, valueOffset, 1L); } /** * Atomically decrements by one the current value. * * @return the previous value */ public final long getAndDecrement() { return unsafe.getAndAddLong(this, valueOffset, -1L); } /** * Atomically adds the given value to the current value. * * @param delta the value to add * @return the previous value */ public final long getAndAdd(long delta) { return unsafe.getAndAddLong(this, valueOffset, delta); } /** * Atomically increments by one the current value. * * @return the updated value */ public final long incrementAndGet() { return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L; } /** * Atomically decrements by one the current value. * * @return the updated value */ public final long decrementAndGet() { return unsafe.getAndAddLong(this, valueOffset, -1L) - 1L; } /** * Atomically adds the given value to the current value. * * @param delta the value to add * @return the updated value */ public final long addAndGet(long delta) { return unsafe.getAndAddLong(this, valueOffset, delta) + delta; } /** * Atomically updates the current value with the results of * applying the given function, returning the previous value. The * function should be side-effect-free, since it may be re-applied * when attempted updates fail due to contention among threads. * * @param updateFunction a side-effect-free function * @return the previous value * @since 1.8 */ public final long getAndUpdate(LongUnaryOperator updateFunction) { long prev, next; do { prev = get(); next = updateFunction.applyAsLong(prev); } while (!compareAndSet(prev, next)); return prev; } /** * Atomically updates the current value with the results of * applying the given function, returning the updated value. The * function should be side-effect-free, since it may be re-applied * when attempted updates fail due to contention among threads. * * @param updateFunction a side-effect-free function * @return the updated value * @since 1.8 */ public final long updateAndGet(LongUnaryOperator updateFunction) { long prev, next; do { prev = get(); next = updateFunction.applyAsLong(prev); } while (!compareAndSet(prev, next)); return next; } /** * Atomically updates the current value with the results of * applying the given function to the current and given values, * returning the previous value. The function should be * side-effect-free, since it may be re-applied when attempted * updates fail due to contention among threads. The function * is applied with the current value as its first argument, * and the given update as the second argument. * * @param x the update value * @param accumulatorFunction a side-effect-free function of two arguments * @return the previous value * @since 1.8 */ public final long getAndAccumulate(long x, LongBinaryOperator accumulatorFunction) { long prev, next; do { prev = get(); next = accumulatorFunction.applyAsLong(prev, x); } while (!compareAndSet(prev, next)); return prev; } /** * Atomically updates the current value with the results of * applying the given function to the current and given values, * returning the updated value. The function should be * side-effect-free, since it may be re-applied when attempted * updates fail due to contention among threads. The function * is applied with the current value as its first argument, * and the given update as the second argument. * * @param x the update value * @param accumulatorFunction a side-effect-free function of two arguments * @return the updated value * @since 1.8 */ public final long accumulateAndGet(long x, LongBinaryOperator accumulatorFunction) { long prev, next; do { prev = get(); next = accumulatorFunction.applyAsLong(prev, x); } while (!compareAndSet(prev, next)); return next; } /** * Returns the String representation of the current value. * @return the String representation of the current value */ public String toString() { return Long.toString(get()); } /** * Returns the value of this {@code AtomicLong} as an {@code int} * after a narrowing primitive conversion. * @jls 5.1.3 Narrowing Primitive Conversions */ public int intValue() { return (int)get(); } /** * Returns the value of this {@code AtomicLong} as a {@code long}. */ public long longValue() { return get(); } /** * Returns the value of this {@code AtomicLong} as a {@code float} * after a widening primitive conversion. * @jls 5.1.2 Widening Primitive Conversions */ public float floatValue() { return (float)get(); } /** * Returns the value of this {@code AtomicLong} as a {@code double} * after a widening primitive conversion. * @jls 5.1.2 Widening Primitive Conversions */ public double doubleValue() { return (double)get(); } }
AtomicLong 示例
//單執行緒下執行緒不安全和AtomicLong的執行緒安全
面試session
- CAS 操作一定是執行緒安全的嗎?
是執行緒相對安全的,沒有絕對的執行緒安全,但是CAS一般情況下比傳統的加鎖併發度會更好,效能更加。但是可能會引起一些問題,如ABA問題,自旋引起的效能問題
ABA問題:執行緒得到 A 時,是其他執行緒先將 A 改為 B ,再將 B 改回 為 A, 執行緒也認為此刻沒有其他執行緒在修改A值 也會成功更新 A.這種情況,大部分情況都是沒問題的,有些具體的業務對ABA問題敏感,就要注意。反正和相關的都想想一想,不然幹就完了。996誰頂住鴨......
ABA問題可以在每個運算元加一個版本號,如 A1 B1 A2,這樣執行緒得到的是A1,和A2比較時,就會發現有其他執行緒在操作A,Java併發包為了解決這個問題,提供了一個帶有標記的原子引用類“AtomicStampedReference”,它可以通過控制變數值的版本來保證CAS的正確性。當然改成傳統的同步加鎖也可以哦。
自旋: 就是指一直迴圈判斷直到成功修改為止。長時間的自旋操作,特別是多個執行緒都在自旋,很影響效能的。可考慮換成鎖來操作。