Java並發之原子變量和原子引用與volatile


我們知道在並發編程中,多個線程共享某個變量或者對象時,必須要進行同步。同步的包含兩層作用:1)互斥訪問(原子性);2)可見性;也就是多個線程對共享的變量互斥地訪問,同時線程對共享變量的修改必須對其他線程可見,也就是所有線程訪問到的都是最新的值。

1. volatile變量和volatile引用

volatile的作用是:保證可見性,但是沒有互斥訪問語義(原子性語義)。volatile能夠保證它修飾的引用以及引用的對象的可見性,volatile不僅保證變量或者引用對所有訪問它的線程的可見性,同時能夠保證它所引用的對象對所有訪問它的線程的可見性。volatile的使用要求滿足下面的兩個條件

1)對變量或者引用的寫操作不依賴於變量或者引用的當前值(如果只有特定的單個線程修改共享變量,那么修改操作也是可以依賴於當前值);

2)該變量或者引用沒有包含在其它的不變式條件中;

volatile最常見的錯誤使用場景是使用volatile來實現並發 i++; 錯誤的原因是,該操作依賴於 i 變量的當前值,他是在 i 變量的當前值的基礎上加一,所以說他依賴於 i 的當前值。多個線程執行 i++; 會丟失更新。比如兩個線程同時讀到 i 的當前值8,都進行加一,然后寫回,最終 i 的結果是 9,而不是我們期待的10,丟失了更新。那么原子變量的引入就是針對volatile的這個缺陷的!!!原子變量的修改操作允許它依賴於當前值所以說”原子變量“是比volatile的語義稍微強化一點!他不僅具有volatile的可見性,同時對原子變量的修改可以依賴於當前值

2. 原子變量和原子引用

從Java 1.5開始引入了原子變量和原子引用:

java.util.concurrent.atomic.AtomicBoolean

java.util.concurrent.atomic.AtomicInteger

java.util.concurrent.atomic.AtomicLong

java.util.concurrent.atomic.AtomicReference

以及他們對應的數組:

java.util.concurrent.atomic.AtomicIntegerArray

java.util.concurrent.atomic.AtomicLongArray

java.util.concurrent.atomic.AtomicReferenceArray

原子變量和引用都是使用compareAndSwap(CAS指令)來實現:依賴當前值的原子修改的。

而且他們的實現都是使用volatile和Unsafe:volatile保證可見性,而Unsafe保證原子性

我們可以稍微分析下AtomicReference的源碼:

public class AtomicReference<V> implements java.io.Serializable {
    private static final long serialVersionUID = -1848883965231344442L;

    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicReference.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile V value;

    /**
     * Creates a new AtomicReference with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicReference(V initialValue) {
        value = initialValue;
    }

    /**
     * Creates a new AtomicReference with null initial value.
     */
    public AtomicReference() {
    }

    /**
     * Gets the current value.
     *
     * @return the current value
     */
    public final V get() {
        return value;
    }

    /**
     * Sets to the given value.
     *
     * @param newValue the new value
     */
    public final void set(V newValue) {
        value = newValue;
    }

    /**
     * Eventually sets to the given value.
     *
     * @param newValue the new value
     * @since 1.6
     */
    public final void lazySet(V newValue) {
        unsafe.putOrderedObject(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(V expect, V update) {
        return unsafe.compareAndSwapObject(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(V expect, V update) {
        return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
    }

    /**
     * Atomically sets to the given value and returns the old value.
     *
     * @param newValue the new value
     * @return the previous value
     */
    @SuppressWarnings("unchecked")
    public final V getAndSet(V newValue) {
        return (V)unsafe.getAndSetObject(this, valueOffset, newValue);
    }

    /**
     * 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 V getAndUpdate(UnaryOperator<V> updateFunction) {
        V prev, next;
        do {
            prev = get();
            next = updateFunction.apply(prev);
        } while (!compareAndSet(prev, next));
        return prev;
    }
......

我們可以看到使用了: private volatile V value; 來保證 value 的可見性;

同時:

    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicReference.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

這段代碼的意思是:獲得AtomicReference<V>實例化對象中的 value 屬性的在該對象在堆內存的偏移 valueOffset 位置,而:

unsafe.compareAndSwapObject(this, valueOffset, expect, update);

的作用就是通過比較valueOffset處的內存的值是否為expect,是的話就更新替換成新值update,這個操作是原子性的。所以volatile保證了可見性,而unsafe保證了原子性。源碼中的UnaryOperator,BinaryOperator等等明顯是模仿C++的,因為Java中沒有函數指針,所以只能使用一元、二元操作對象來實現相應的功能。

3. Unsafe

Unsafe的源碼可以參見:http://www.docjar.com/html/api/sun/misc/Unsafe.java.html

他的實現主要是通過編譯器,利用CPU的一些原子指令來實現的。

Most methods in this class are very low-level, and correspond to a
small number of hardware instructions (on typical machines).  Compilers
are encouraged to optimize these methods accordingly.

4. LongAdder(加法器)

在jdk 1.8中又引入了進過充分優化的原子變量“加法器”:java.util.concurrent.atomic.LongAdder,它的性能在和其他原子變量以及volatile變量相比都是最好的,所以在能使用LongAdder的地方就不要使用其它原子變量了。但是LongAdder中並沒有提供:依賴於當前變量的值來修改的操作。一般用於實現並發計數器是最好的。

LongAdder實現下列接口:

public void add(long x);    // 加 x   
public void increment();   // 加1
public void decrement();  // 減1
public long sum();            // 求和
public void reset();          // 重置0
public String toString() {
        return Long.toString(sum());
    }

    /**
     * Equivalent to {@link #sum}.
     *
     * @return the sum
     */
    public long longValue() {
        return sum();
    }

    /**
     * Returns the {@link #sum} as an {@code int} after a narrowing
     * primitive conversion.
     */
    public int intValue() {
        return (int)sum();
    }

    /**
     * Returns the {@link #sum} as a {@code float}
     * after a widening primitive conversion.
     */
    public float floatValue() {
        return (float)sum();
    }

    /**
     * Returns the {@link #sum} as a {@code double} after a widening
     * primitive conversion.
     */
    public double doubleValue() {
        return (double)sum();
    }

 總結:

就同步語義而言,volatile < 原子變量/原子引用 < lock/synchronized. volatile只保證可見性;原子變量和原子引用保證可見性的同時,利用CAS指令實現了原子修改——“修改操作允許它依賴於當前值”;而lock/synchronized則同時保證可見性和互斥性(原子性)。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM