1. 程式人生 > >執行緒記憶體的可見性

執行緒記憶體的可見性

先看下面的程式碼和執行的結果:

package hello_java;

public class MemoryVisiable {
    public static void main(String[] args) {
        ShareData02 shareData = new ShareData02();
        new  Thread(() ->{
            shareData.flag = true;
        }).start();

        while(true){
            if (shareData.flag){
                System.out.println("flag is true....");
                break;
            }
        }
        System.out.println("Main .... ");
    }
}

class ShareData02{
    public boolean flag ;
}

flag is true....
Main .... 

執行結果為如上。

對這個程式碼稍作修改:

package hello_java;

public class MemoryVisiable {
    public static void main(String[] args) {
        ShareData02 shareData = new ShareData02();
        new  Thread(() ->{
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            shareData.flag = true;
        }).start();

        while(true){
            if (shareData.flag){
                System.out.println("flag is true....");
                break;
            }
        }
        System.out.println("Main .... ");
    }
}

class ShareData02{
    public boolean flag ;
}

如此,如果運行當前的程式的話會發現,程式進入了死迴圈,為什麼會發生死迴圈呢?下面就是解釋。

記憶體可見性:

如上圖中的記憶體模型,執行緒1會有一個與之對應的工作記憶體(這裡的工作記憶體就是棧),執行緒1 和執行緒2 都想要操作主存中的某一個資源,但是不會直接操作主存中的資源而是將主存中的值複製到對應的工作記憶體中,在圖的情景中,執行緒1對自己對應的工作記憶體中操作資源,將其修改為 a ,理論上講,執行緒1 應該將值同步到主存中,但是執行緒1 可能還會操作該資源,這樣頻繁的互動,會影響效率,所以java暫時不會同步資料到主存中,所以,所以,此時執行緒2 ,無法獲取到已經被修改的資源 也就是 a這個值。

記憶體的可見性:資料可能由於記憶體的可見性的原因在多個執行緒中看不見

我們上面的程式出現死迴圈的結果(兩個執行緒,一個在讀,一個在改)就是因為上述的原因,那麼如何解決這個問題呢?先來看看效果:

package hello_java;

public class MemoryVisiable {
    public static void main(String[] args) {
        ShareData02 shareData = new ShareData02();
        new  Thread(() ->{
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            shareData.flag = true;
        }).start();

        while(true){
            if (shareData.flag){
                System.out.println("flag is true....");
                break;
            }
        }
        System.out.println("Main .... ");
    }
}

class ShareData02{
    public volatile boolean flag ;
}

flag is true....
Main .... 

由上面的內容可以看出,我們在共享資料的部分添加了 volatile 關鍵字,這個關鍵字可以做到,如果在工作記憶體中修改了共享資料,那麼立即同步到記憶體中,那麼其他的執行緒就能夠獲取(共享)到這個資料。接著我們來看看另外的一種解決方法:

package hello_java;

public class MemoryVisiable {
    public static void main(String[] args) {
        ShareData02 shareData = new ShareData02();
        new  Thread(() ->{
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            shareData.flag = true;
        }).start();

        while(true){
            if (shareData.flag){
                System.out.println("flag is true....");
                break;
            }
            System.out.println(shareData.flag);
        }
        System.out.println("Main .... ");
    }
}

class ShareData02{
    public  boolean flag ;
}


false
false
false
flag is true....
Main .... 

為什麼會這樣呢?原因在於println方法,我們檢視該方法的原始碼:

    public void println(boolean x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

會發現該方法新增上了  synchronized 關鍵字,表明該方法加鎖了,只要某執行緒加上了鎖,他會將自己工作記憶體中的資料同步到主存中,然後將自己的工作記憶體中的資料清空,然後在從主存中讀取資料到工作記憶體中。所以這裡並不是println方法能夠解決這個問題,而是加鎖之後能解決這個問題。