java 中輕量級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 ,使得方法在多執行緒單一持有,再也不會出現上面的問題了。