volatile關鍵字用法以及執行緒資料可見性的問題
最近在研究ConcurrentHashMap
的原始碼的時候,發現底層實現的Segments
使用到了關鍵字volatile
不太明白這個關鍵字的用法,查了一些資料總結如下:
volatile 的作用是讓變數在多個執行緒可見。
說實話這個定義也有點籠統,既然看原始碼了肯定是想知道具體是怎麼讓多執行緒可見的。
實際上Java裡每個執行緒工作的時候,都會有自己的一個執行緒私有工作記憶體,裡面存放著只有本執行緒可見的變數,這些執行緒共同連線了一個的公有記憶體(我理解為Java程式的主記憶體區)。執行緒為了執行的效率,除了首次從公有記憶體獲取資料外,讀取的資料都是本執行緒私有工作記憶體的資料(看到有書上說只有JVM設定-server
這樣的結果就是私有工作記憶體的值和公有記憶體的值不同步,即使本執行緒修改了一個數值,本執行緒讀取的也可能是錯誤的數值。為了驗證這個問題,編寫實驗程式碼如下:
class TestThread extends Thread{
private boolean flag = false;
public void setFlag(boolean flag){
this.flag = flag;
}
@Override
public void run() {
for (;;) {
if(flag){
System.out.println("迴圈結束");
System.exit(0);
}
}
}
public static void main(String[] args) throws InterruptedException {
TestThread a = new TestThread();
a.start();
//加入休眠,保證執行緒執行之後才設定標識
Thread.sleep(10);
a.setFlag(true);
}
}
大家可以執行一下,以上程式碼會死迴圈。
其原因就是我們修改的flag是公共堆疊的flag,而執行緒讀取的則是執行緒私有堆疊的flag,兩個變數沒有同步(主要是a執行緒一直讀取自己的私有堆疊),所以執行緒沒有終止,此時我們給flag變數加上volatile
關鍵字,上述程式執行正常。
private volatile boolean flag = false;
總結
綜上,volatile
關鍵字的作用是,強制變數每次都讀取公共記憶體,這樣,一旦有執行緒改變變數的值,其他執行緒馬上就能發現。這就是定義裡,所有執行緒可見的含義。
但是要注意,此關鍵字只能保證變數的可見性,不能保證原子性,也不會阻塞執行緒。也就是說多個執行緒併發修改變數時,還是會有併發性問題。要解決併發性的問題,還是隻能用synchronized關鍵字。應用的場景,就是一旦改動,所有執行緒都能馬上感知到的的情況。例如示例程式碼設定子執行緒停止迴圈的標誌。