volatile 修飾的變數具備兩種特性:
cpu修改資料,首先是對快取的修改,然後再同步回主存,在同步回主存的時候,如果其他cpu也快取了這個資料,就會導致其他cpu快取上的資料失效(通過嗅探匯流排資料傳播,檢查快取對應的主存位址是否被修改過),這樣,當其他cpu再去它的快取讀取這個資料的時候發現快取已失效,就必須從主存重新獲取
注意:執行緒安全必須保證原子性,可見性,有序性。而volatile只能保證可見性和有序性。
所以基於 volatile 變數的運算的復合操作在併發下不一定是安全的。
理解volatile終極方法:volatile變數的單個讀/寫,看成是使用同乙個鎖對這些單個讀/寫操作做了同步。
class example
system.out.println("thread1 finish loop,i=" + i);
}public boolean getstop()
public void setstop(boolean flag)
}public class volatileexample
});t1.start();
thread.sleep(1000);
system.out.println("主線程即將置stop值為true...");
example.setstop(true);
system.out.println("主線程已將stop值為:" + example.getstop());
system.out.println("主線程等待執行緒1執行完...");
t1.join();
system.out.println("執行緒1已執行完畢,整個流程結束...");}}
當讀乙個 volatile 變數時,jmm 會把該執行緒對應的本地記憶體置為無效(主存資料有修改後)。執行緒接下來將從主記憶體中讀取共享資料並覆蓋快取
當寫乙個 volatile 變數時,jmm 會把該執行緒,先覆蓋快取,且將共享變數值刷到主記憶體
重排序分為編譯器重排序和處理器重排序。為了實現volatile記憶體語義,jmm會分別限制這兩種型別的重排序型別
在每個volatile寫操作的前面插入乙個storestore屏障。
在每個volatile寫操作的後面插入乙個storeload屏障。
在每個volatile讀操作的後面插入乙個loadload屏障。
在每個volatile讀操作的後面插入乙個loadstore屏障。
版本1:(執行緒不安全的)
public class singleton
private static singleton instance;
public static singleton getinstance()
return instance;
}}
版本2:(執行緒不安全的)採用同步加鎖的方式:
public class singleton
private static singleton instance;
public static singleton getinstance()
}return instance;
}}
如果getinstance()方法被多個執行緒頻繁的呼叫,乙個執行緒獲取鎖,進去建立物件。其他執行緒將阻塞掛起。導致程式執行效能的下降。
通過雙重檢查,定來降低同步的開銷。如果第一次檢查instance不為null,那麼就不需要執行下面的加鎖和初始
化操作。因此,可以大幅降低synchronized帶來的效能開銷
版本3:(執行緒不安全的)
雙重檢查
//雙重檢測
public class singleton
private static singleton instance;
public static singleton getinstance()}}
return instance;
}}
第二次檢查的作用:在於當執行緒兩個執行緒 a、b都通過了第一次的 if (instance == null) 的判斷的情況。
流程:假設有兩個執行緒 a、b 同時呼叫 getinstance() 方法,他們會同時發現 instance == null ,於是同時對 singleton.class 加鎖,此時 jvm 保證只有乙個執行緒能夠加鎖成功(假設是執行緒 a),另外乙個執行緒則會處於等待狀態(假設是執行緒 b);執行緒 a 會建立乙個 singleton 例項,之後釋放鎖,鎖釋放後,執行緒 b 被喚醒,執行緒 b 再次嘗試加鎖,此時是可以加鎖成功的,加鎖成功後,執行緒 b 檢查 instance == null 時會發現,已經建立過 singleton 例項了,所以執行緒 b 不會再建立乙個 singleton 例項。
看似流程沒問題,實際不堪一擊。因為忽略了指令重拍。
instance = new singleton();操作並不是乙個原子性指令,會被分為多個指令:
發生指令重排後:
所以a執行緒執行完重排後的第二步,還未執行初始化物件的時候。b執行緒就來取instance時,發現instance不為空,於是便直接返回該值,結果便返回null.
版本4:(執行緒安全的)
volatile禁止指令重排。
public class singleton
private volatile static singleton instance;
static singleton getinstance()
}return instance;
}}
懶漢變餓漢式:(執行緒安全的)instance = new singleton();一樣會指令重排,但對於非構造執行緒是不可見的。
public class singleton
public static singleton instance = new singleton();
public static instance getinstance()
}
Volatile如何保證可見性
首先要知道記憶體屏障是什麼,記憶體屏障是乙個cpu指令,記憶體屏障是這樣的指令 1,確保特定操作執行的順序 2,影響一些資料的可見性,編譯器和cpu可以保證輸出結果一樣的前提下對指令進行重排序,使得效能優化,當插入乙個記憶體屏障,相當於告訴cpu和編譯器,先於這個命令的必須先執行,後於這個命令的必須...
volatile 關鍵字的如何保證記憶體可見性
volatile關鍵字的作用 保證記憶體的可見性 防止指令重排 注意 volatile 並不保證原子性 記憶體可見性 volatile保證可見性的原理是在每次訪問變數時都會進行一次重新整理,因此每次訪問都是主記憶體中最新的版本。所以volatile關鍵字的作用之一就是保證變數修改的實時可見性。當且僅...
volatile關鍵字如何保證記憶體可見性
一 記憶體可見性 例子1 有乙個全域性的狀態變數open boolean open true 這個變數用來描述對乙個資源的開啟關閉狀態,true表示開啟,false表示關閉,假設有乙個執行緒a,在執行一些操作後將open修改為false 執行緒a resource.close open false ...