1. 程式人生 > 其它 >java 中輕量級volatile同步欄位解釋

java 中輕量級volatile同步欄位解釋

技術標籤:Javajava多執行緒volatile

前言:

最近在整理多執行緒的一些用法,其中有涉及到volatile 關鍵字,沒有深入研究,只是簡單的驗證了一下,這裡記錄一下心得。

一.volatile 是輕量級的

為啥一定要強調輕量級?

1.volatile 只能修飾變數

2.保證不同執行緒操作時變數的可見性,即一個執行緒修改了變數,另一個執行緒立馬可見

3. 不能確保執行緒同步(這個下面會舉例說明)

二. 變數可見性驗證

幾乎所有的部落格都會舉下面這個例子說明


public class VolatileTest extends Thread {
    boolean flag = false;
    int i = 0;

    public void run() {
        while (!flag) {   // 如果為true會馬上結束迴圈
            i++;
        }
    }

    public static void main(String[] args) throws Exception {
        VolatileTest vt = new VolatileTest();
        vt.start();
        Thread.sleep(10);
        System.out.println(vt.isAlive());
        vt.flag = true;   //設定為true,意圖停止迴圈
        System.out.println("store:" + vt.i);
        Thread.sleep(10);
        System.out.println(vt.isAlive());
    }
}

列印一下結果:

true
store:7517191
true

程序沒有結束,並且陷入死迴圈,跟想要的結果相去甚遠。

這裡其實涉及到java記憶體模型的問題,有點複雜,這裡簡單說一下結論:

上述程式碼中的main主執行緒啟動了 vt 執行緒, vt 執行緒啟動後設置 flag為 false,vt 執行緒在執行的時候flag值是不會自動更新的(vt.flag 就沒有意義了),所以flag一直是false。


public class VolatileTest extends Thread {
    volatile boolean flag = false;
    int i = 0;

    public void run() {
        while (!flag) {   // 如果為true會馬上結束迴圈
            i++;
        }
    }

    public static void main(String[] args) throws Exception {
        VolatileTest vt = new VolatileTest();
        vt.start();
        Thread.sleep(10);
        System.out.println(vt.isAlive());
        vt.flag = true;   //設定為true,意圖停止迴圈
        System.out.println("store:" + vt.i);
        Thread.sleep(10);
        System.out.println(vt.isAlive());
    }
}

加上 volatile 關鍵字以後,執行緒在使用 flag 的時候會主動去更新值,這樣 vt.flag = true 就會生效,vt 執行緒就退出迴圈逐步結束,輸出結果如下

true
store:7537387
false

三.volatile 不能確保執行緒同步

大家可能會覺得,既然volatile 可以保證變數是及時可見的,那為啥又不能保證執行緒同步?別急,這裡用事實說話

public class VolatileTestSyn {
    public volatile int inc = 0;

    public void increase() {
        inc++;
    }

    public static void main(String[] args) {
        final VolatileTestSyn test = new VolatileTestSyn();
        for (int i = 0; i < 5; i++) {
            new Thread() {
                public void run() {
                    for (int j = 0; j < 1000; j++)
                        test.increase();
                }
            }.start();
        }
        while (Thread.activeCount() > 2)  //保證前面的執行緒都執行完
            Thread.yield();
        System.out.println("result:"+test.inc);
    }
}

列印結果(機器效能高的話可能還是5000,可以適當調大迴圈次數或執行緒個數):

4658

如果執行緒是同步的,那結果肯定是5000沒跑,結果卻不是,為啥?

多個執行緒同時操作 inc,每次取到的 inc 都是計算後寫到記憶體的最新值,按道理不應該出錯;但是,假如 A 執行緒啟動取得的 inc 是100,這時候 B 執行緒也啟動取得 inc 100;A 執行緒執行 +1 操作把 101 寫到記憶體,但是這時候 B 執行緒短暫暫停(注意,B執行緒已經取得 inc 為 100,B執行緒不會再更新 inc 了);C 執行緒啟動獲取 inc 為101,執行 +1 操作,並把102 寫到記憶體,而後很快 B 執行緒也執行了 +1 操作,把 101 寫到記憶體覆蓋了 C執行緒的結果;這時候 D執行緒啟動獲取的 inc 是 101........這就解釋了為啥上面結果不是 5000。(這塊涉及 Java 記憶體模型,想深究的同學可以研究一下)

所以這時候就需要給increase() 方法加一個synchronized ,使得方法在多執行緒單一持有,再也不會出現上面的問題了。

四. 參考部落格

Java併發程式設計:volatile關鍵字解析

Java中Volatile關鍵字詳解