1. 程式人生 > 實用技巧 >AtomicLong原始碼淺析(基於jdk1.8.0_231)

AtomicLong原始碼淺析(基於jdk1.8.0_231)

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}\)
和 addA(A) 不相等(說明修改前的準備階段到準備修改期間,有別的執行緒在操作A,改了A的值), 然後記住此刻此地址A 記作addA(A),再從第一步開始,直到該執行緒成功更新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的正確性。當然改成傳統的同步加鎖也可以哦。
    自旋: 就是指一直迴圈判斷直到成功修改為止。長時間的自旋操作,特別是多個執行緒都在自旋,很影響效能的。可考慮換成鎖來操作。