volatile保證原子性嗎?

參考網頁

http://www.javashuo.com/article/p-hsugcpqq-hw.htmlhtml

JDK版本--代碼中打印了jdk的版本

1.7.0_79java

例子代碼--Test.java

package volatileTest;


/**

 * @Description TODO

 * @Date 2018/8/18 11:58

 * @Version 1.0

 **/

public class Test {

    public volatile int inc = 0;


    public void increase() {

        inc++;

    }


    public static void main(String[] args) {

     System.out.println(System.getProperty("java.version"));

    

    

        final Test test = new Test();

        for(int i=0;i<10;i++){

            new Thread(){

                public void run() {

                    for(int j=0;j<1000;j++)

                        test.increase();

                };

            }.start();

        }


        while(Thread.activeCount()>1)  //保證前面的線程都執行完

            Thread.yield();

        System.out.println("---" + test.inc);

    }


}

運行結果--屢次運行

運行結果1

1.7.0_79c++

---9836緩存

運行結果2

1.7.0_79app

---10000spa

運行結果3

1.7.0_79線程

---9000code

運行結果4

1.7.0_79htm

---9882blog

運行結果5

1.7.0_79

---10000

運行結果6

1.7.0_79

---10000

結論

結果有10000,也有幾回不是10000的。不肯定,不穩定,也就是說volatile沒法保證操做的原子性。

爲何volatile沒法保證操做的原子性?

做者解釋

你們想一下這段程序的輸出結果是多少?也許有些朋友認爲是10000。可是事實上運行它會發現每次運行結果都不一致,都是一個小於10000的數字。

可能有的朋友就會有疑問,不對啊,上面是對變量inc進行自增操做,因爲volatile保證了可見性,那麼在每一個線程中對inc自增完以後,在其餘線程中都能看到修改後的值啊,因此有10個線程分別進行了1000次操做,那麼最終inc的值應該是1000*10=10000。

這裏面就有一個誤區了,volatile關鍵字能保證可見性沒有錯,可是上面的程序錯在沒能保證原子性。可見性只能保證每次讀取的是最新的值,可是volatile沒辦法保證對變量的操做的原子性

在前面已經提到過,自增操做是不具有原子性的,它包括讀取變量的原始值、進行加1操做、寫入工做內存。那麼就是說自增操做的三個子操做可能會分割開執行,就有可能致使下面這種狀況出現:

假如某個時刻變量inc的值爲10,

線程1對變量進行自增操做,線程1先讀取了變量inc的原始值,而後線程1被阻塞了;

而後線程2對變量進行自增操做,線程2也去讀取變量inc的原始值,因爲線程1只是對變量inc進行讀取操做,而沒有對變量進行修改操做,因此不會致使線程2的工做內存中緩存變量inc的緩存行無效,因此線程2會直接去主存讀取inc的值,發現inc的值是10,而後進行加1操做,並把11寫入工做內存,最後寫入主存。

而後線程1接着進行加1操做,因爲已經讀取了inc的值,注意此時在線程1的工做內存中inc的值仍然爲10,因此線程1對inc進行加1操做後inc的值爲11,而後將11寫入工做內存,最後寫入主存

那麼兩個線程分別進行了一次自增操做後,inc只增長了1。

解釋到這裏,可能有朋友會有疑問,不對啊,前面不是保證一個變量在修改volatile變量時,會讓緩存行無效嗎?而後其餘線程去讀就會讀到新的值,對,這個沒錯。這個就是上面的happens-before規則中的volatile變量規則,可是要注意,線程1對變量進行讀取操做以後,被阻塞了的話,並無對inc值進行修改。而後雖然volatile能保證線程2對變量inc的值讀取是從內存中讀取的,可是線程1沒有進行修改,因此線程2根本就不會看到修改的值。

根源就在這裏,自增操做不是原子性操做,並且volatile也沒法保證對變量的任何操做都是原子性的。

★網友回答

★一個基本常識--計算機執行指令只會向前,不會後退的

Inc++的操做分爲3步

【操做1】(由於 volatile 的緣由)CPU從主存中獲取inc的值,此時inc的值已經到了CPU中

【操做2】CPU對 inc 的值進行 +1 的操做

【操做3】CPU將 inc + 1 後的值寫回到工做內存,而後寫回到主存

★爲何volatile沒法保證操做的原子性?

實際上沒有那麼多的爲何。

由於java規範中原本也沒說volatile要保證原子性啊。

假設有線程A和線程B。假設此時此刻inc的值爲10。

線程A進行了【操做1】,而後失去了鎖。注意,此時線程A已經讀取了inc的值,線程A不會再讀取第二次了。因爲inc被volatile修飾,此時線程A能保證本身讀取的inc確定是最新的值。可是,計算機指令是不會回退的,只會向前進行

而後線程B完成了【操做1】【操做2】【操做3】,而後把鎖交還給了線程A。注意,此時,主存中inc值已經變成了加1後的值即11。可是,計算機指令是不會回退的,只會向前進行,因此線程A會繼續按照讀取的inc爲10進行後面的指令,因此線程A會繼續【操做2】【操做3】,而後再次把inc加1後的值即11寫回主存。

因此,雖然線程A和線程B都進行了 +1 的操做,可是inc的值仍是11。

相關文章
相關標籤/搜索