http://www.javashuo.com/article/p-hsugcpqq-hw.htmlhtml
1.7.0_79java
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.7.0_79c++
---9836緩存
1.7.0_79app
---10000spa
1.7.0_79線程
---9000code
1.7.0_79htm
---9882blog
1.7.0_79
---10000
1.7.0_79
---10000
結果有10000,也有幾回不是10000的。不肯定,不穩定,也就是說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也沒法保證對變量的任何操做都是原子性的。
【操做1】(由於 volatile 的緣由)CPU從主存中獲取inc的值,此時inc的值已經到了CPU中
【操做2】CPU對 inc 的值進行 +1 的操做
【操做3】CPU將 inc + 1 後的值寫回到工做內存,而後寫回到主存
實際上沒有那麼多的爲何。
由於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。