一、前言
java內存模型中的可見性是指,當一個線程修改了共享變量的值後,其他線程可以立馬知道這個修改後的值。在《Java併發編程:volatile關鍵字解析》中有這麼一段話:
對於可見性,Java提供了volatile關鍵字來保證可見性。
當一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,它會去內存中讀取新值。
而普通的共享變量不能保證可見性,因爲普通共享變量被修改之後,什麼時候被寫入主存是不確定的,當其他線程去讀取時,此時內存中可能還是原來的舊值,因此無法保證可見性。
對於這一論點,網上鮮有論據。所以,筆者通過幾個簡單的例子來證明volatile確實可以保證可見性。
二、論證過程
每個線程都有一個工作內存,線程的工作內存中保存了被該線程使用到的變量的主內存副本拷貝,線程對變量的所有操作都必須在工作內存中進行,而不能直接讀寫主內存中的變量。不同線程之間也無法直接訪問對方工作內存中的變量,線程間變量的傳遞依靠主內存來完成。
以此原理,設計瞭如下的例子:
package ThreadPool;
public class VolatileTest {
public boolean isShutdown;
public boolean getShutdown () {
return isShutdown;
}
public void shutdown () {
isShutdown = true;
}
public class ReaderThread extends Thread {
@Override
public void run() {
try {
System.out.println("開始循環");
while (!isShutdown) {
};
System.out.println("結束循環");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class WatchThread extends Thread {
@Override
public void run() {
shutdown();
}
}
public static void main(String[] args){
try {
VolatileTest volatileTest = new VolatileTest();
volatileTest.new ReaderThread().start();
//讓主線程睡眠一秒,確保另一個線程調用shutdown方法時死循環已經開始
Thread.sleep(1000);
volatileTest.new WatchThread().start();
//此刻的睡眠是爲了確保shutdown方法對isShutdown變量的修改已經同步到主內存
Thread.sleep(1000);
//打印isShutdown的值
System.out.println("getShutdown:" + volatileTest.getShutdown());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
isShutdown是共享變量,默認值是false。ReaderThread線程的執行體是一個死循環,直到isShutdown變爲true才跳出循環,打印“結束循環”。WatchThread線程的執行體調用了shutdown方法,將isShutdown的值改爲true。如果說ReaderThread線程跳出了死循環,那麼就說明WatchThread線程對isShutdown的修改是對其可見的,反之,則不可見。
運行結果如下:
由此可見,WatchThread對isShutdown變量的修改是生效的,但ReaderThread並沒有跳出循環,也就意味着ReaderThread並沒有讀取到isShutdown變量的最新值,而是讀取工作內存中的舊值。所以,普通變量是不具有可見性的。
緊接着,我們用volatile去修飾isShutdown變量。
運行結果如下:
這一次,ReaderThread跳出了死循環,打印出了“結束循環”,這說明ReaderThread可以讀取到isShutdown變量的最新值,換句話說就是,WatchThread對isShutdown變量的修改對於ReaderThread是可見的。
三、總結
綜上所述,volatile確實可以保證可見性。