java volatile不能保證原子性

Java中long和double賦值不是原子操做,由於先寫32位,再寫後32位,分兩步操做,這樣就線程不安全了。若是改爲下面的就線程安全了
private volatile long number = 8;
那麼,爲何是這樣?volatile關鍵字難道能夠保證原子性?
java程序員很熟悉的一句話:volatile僅僅用來保證該變量對全部線程的可見性,但不保證原子性。可是咱們這裏的例子,volatile彷佛是有時候能夠代替簡單的鎖,彷佛加了volatile關鍵字就省掉了鎖。這不是互相矛盾嗎?java

其實若是一個變量加了volatile關鍵字,就會告訴編譯器和JVM的內存模型:這個變量是對全部線程共享的、可見的,每次jvm都會讀取最新寫入的值並使其最新值在全部CPU可見。因此說的是線程可見性,沒有提原子性。程序員

下面咱們用一個例子說明volatile沒有原子性,不要將volatile用在getAndOperate場合(這種場合不原子,須要再加鎖,如i++),僅僅set或者get的場景是適合volatile的。
例如你讓一個volatile的integer自增(i++),其實要分紅3步:1)讀取volatile變量值到local; 2)增長變量的值;3)把local的值寫回,讓其它的線程可見。這3步的jvm指令爲:緩存

mov    0xc(%r10),%r8d ; Load
inc    %r8d           ; Increment
mov    %r8d,0xc(%r10) ; Store
lock addl $0x0,(%rsp) ; StoreLoad Barrier

注意最後一步是內存屏障。
什麼是內存屏障(Memory Barrier)?
內存屏障(memory barrier)是一個CPU指令。基本上,它是這樣一條指令: a) 確保一些特定操做執行的順序; b) 影響一些數據的可見性(多是某些指令執行後的結果)。編譯器和CPU能夠在保證輸出結果同樣的狀況下對指令重排序,使性能獲得優化。插入一個內存屏障,至關於告訴CPU和編譯器先於這個命令的必須先執行,後於這個命令的必須後執行。內存屏障另外一個做用是強制更新一次不一樣CPU的緩存。例如,一個寫屏障會把這個屏障前寫入的數據刷新到緩存,這樣任何試圖讀取該數據的線程將獲得最新值,而不用考慮究竟是被哪一個cpu核心或者哪顆CPU執行的。安全

內存屏障(memory barrier)和volatile什麼關係?上面的虛擬機指令裏面有提到,若是你的字段是volatile,Java內存模型將在寫操做後插入一個寫屏障指令,在讀操做前插入一個讀屏障指令。這意味着若是你對一個volatile字段進行寫操做,你必須知道:一、一旦你完成寫入,任何訪問這個字段的線程將會獲得最新的值。二、在你寫入前,會保證全部以前發生的事已經發生,而且任何更新過的數據值也是可見的,由於內存屏障會把以前的寫入值都刷新到緩存。
下面的測試代碼能夠實際測試voaltile的自增沒有原子性:jvm

/**
 * Created by lhw on 16-7-29.
 */
public class Demo {
    private static volatile long _longVal = 0;

    public static void main(String[] args) {

        Thread t1 = new Thread(new LoopVolatile());
        t1.start();

        Thread t2 = new Thread(new LoopVolatile2());
        t2.start();

        while (t1.isAlive() || t2.isAlive()) {
        }

        System.out.println("final val is: " + _longVal);
    }

    private static class LoopVolatile implements Runnable {
        public void run() {
            long val = 0;
            while (val < 10000000L) {
                _longVal++;
                val++;
            }
        }
    }


    private static class LoopVolatile2 implements Runnable {
        public void run() {
            long val = 0;
            while (val < 10000000L) {
                _longVal++;
                val++;
            }
        }
    }
}
第一次結果:final val is: 18683425
第二次結果:final val is: 15542661
第三次結果:final val is: 18549393

很明顯,輸出結果不一致,說明volatile不能保證原來子性。oop

相關文章
相關標籤/搜索