我們知道在並發編程中,多個線程共享某個變量或者對象時,必須要進行同步。同步的包含兩層作用: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則同時保證可見性和互斥性(原子性)。