volatile 關鍵字的深刻分析及AtomicInteger的使用

volatile的語義:
一、保證被volatile修飾的變量對全部其餘的線程的可見性。
二、使用volatile修飾的變量禁止指令重排優化。
看代碼:java

public class InheritThreadClass extends Thread{
    private static volatile int a = 0;
    @Override
    public void run() {
        for(int i = 0; i < 1000; i++){
            a++;
        }
    }
    public static void main(String[] args) {
        InheritThreadClass[] threads = new InheritThreadClass[100];
        for(int i=0; i < 100; i++){
            threads[i] = new InheritThreadClass();
            threads[i].start();
        }
        //等待全部子線程結束
        while(Thread.activeCount() > 1){
            Thread.yield();
        }
        
        //這段代碼會在全部子線程運行完畢以後執行
        System.out.println(a);  //(1)
    }
}

上面的代碼中建立了100個線程,而後在每一個線程中對變量a進行了1000次的自增運算,那麼也就意味着,若是這段代碼能夠正確的併發運行,最後在代碼(1)處應該輸出100000。可是屢次運行你會發現每次輸出的結果並非咱們預期的那樣,而都是小於等於100000。也就是說每次運行的結果是不固定的不同的,這是爲何呢? 由於經過上面volatile關鍵字的語義咱們知道被該關鍵字修飾的變量對全部的線程是可見的啊,那怎麼會出現這種狀況呢?難道語義有錯? 那是不可能的,語義確定是沒有錯的。安全

咱們知道每個線程都有本身的私有內存,而線程之間的通訊是經過主存來實現的,volatile在這裏保證多線程的可見性的意思是說:若是一個線程修改了被volatile關鍵字修飾的變量,會立馬刷新到主內存中,其餘須要使用這個變量的線程不在從本身的私有內存中獲取了,而是直接從主內存中獲取。雖然volatile關鍵字保證了變量對全部線程的可見性,可是java代碼中的運算操做並不是原子操做。多線程

咱們使用javap命令查看字節碼(javap -verbose InheritThreadClass.class)會發如今虛擬機中這個自增運算使用了4條指令(getstatic, iconst_1, iadd, putstatic)。 當getstatic指令把a的值壓入棧頂時,volatile關鍵字保證了a的值此時是正確的,可是在執行iconst_1iadd這些指令時其餘線程有可能已經把a的值加大了,而已經在操做棧頂的值就變成了過時的數據了,因此putstatic指令執行後可能又把較小的a值同步回主內存了。 因此它不是一個原子運算,所以在多線程的狀況下它並非一個安全的操做。其實這麼說也不是最嚴謹的,由於即便通過編譯後的字節碼只使用了一條指令進行運算也不表明這條指令就是原子操做。由於一條字節碼指令在解釋執行時,解釋器須要運行許多行代碼才能實現該條指令的語義,而即便是編譯執行,一條字節碼指令也可能須要轉化成多條本地機器碼指令。併發

因此有關volatile的變量對其餘線程的」可見性「的語義描述並不能得出這樣的結論:基於volatile變量的運算在高併發下是安全的。ide

那這種在高併發下的自增運算如何作到線程安全呢?可使用synchronized,可是加鎖的話性能開銷太大,高併發下不是一個明智之選。可使用併發包java.util.concurrent.atomic下的AtomicInteger原子類。
看代碼:高併發

private static volatile AtomicInteger a = new AtomicInteger(0);
    
    @Override
    public void run() {
        for(int i = 0; i < 1000; i++){
            a.getAndIncrement();
        }
    }

上面的代碼就能夠在高併發下正確的運行,每次輸出都是100000。
看AtomicInteger源碼:性能

**//部分關鍵字段**
private static final Unsafe unsafe = Unsafe.getUnsafe();
/*
  valueOffset這個是指類中相應字段在該類的偏移量, 在下面的靜態塊中調用objectFieldOffset()方法初始化。
*/
private static final long valueOffset;

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

//objectFieldOffset方法是一個本地方法
public native long objectFieldOffset(Field field);

// AtomicInteger的構造器之一
public AtomicInteger(int initialValue) {
    value = initialValue;
}
//getAndIncrement()這個方法的源碼實現
public final int getAndIncrement() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return current;
    }
}
//get()方法的實現
public final int get() {
    return value;
}
/*compareAndSet(int expect, int update)方法內部又直接調用了
 *unsafe的compareAndSwapInt方法,這裏直接上compareAndSwapInt源碼的實現
 *在obj的offset位置比較內存中的值和指望的值,若是相同則更新。
 *這是一個本地方法,應該是原子的,所以提供了一種不可中斷的方式更新
*/
public native boolean compareAndSwapInt(Object obj, long offset,  
                                            int expect, int update);
相關文章
相關標籤/搜索