volatile是java語言中的一個關鍵字,經常使用於併發編程,有兩個重要的特色:具備可見性,java虛擬機實現會爲其知足Happens before
原則;不具有原子性.用法是修飾變量,如:volatile int i
.html
介紹其可見性先從cpu,cpu緩存和內存的關係入手.java
cpu緩存是一種加速手段,cpu查找數據時會先從緩存中查找,若是不存在會從內存中查找,因此若是緩存中數據和內存中數據不一致,cpu處理數據的一致性就沒法保證.從機器語言角度來說,有一些一致性協議來保證緩存一致,可是本文主要從抽象角度解釋volatile
爲什麼能保證可見性.對於volatile
變量的賦值,會刷入主內存,而且通知其餘cpu核心,大家緩存中的數據無效了,這樣全部cpu核心再想對該volatile
變量操做首先會從主內存中從新拉取值.這就保證了對於cpu操做的數據是最新.編程
可是這並不能保證volatile
修飾的變量的原子性.讓咱們想一想一個場景,變量volatile int count
存儲在內存中,cpu核心1和cpu核心2同時讀取該數據,並存入緩存,而後進行count++
操做.count++
實際能夠分解爲三步:緩存
int tmp = count; tmp = count + 1; count = tmp;
當count = tmp
執行結束,cpu會把count
刷入內存並通知其餘cpu緩存無效,若是兩個cpu核心同時將其刷入了內存,通知了緩存無效,那麼咱們是否是隻獲得了count = 2
,是否是丟失了一個+1的值.因此不要試圖用volatile
保證多步操做的原子性,原子性能夠經過synchronized
進行維護.多線程
須要注意一點,long
類型和double
類型的數據長度是64位的,JVM規範容許對於64位類型數據分開賦值,即高位32位和低位32位能夠分開賦值,對於這種狀況可使用volatile
修飾保證其賦值是一次完成的.可是!!!雖然JVM是這樣規定的,絕大多數虛擬機仍是實現了64位數據賦值的原子性,即便不使用volatile
關鍵字進行修飾也不會出現讀取到只賦值一半的64位類型數據,因此沒必要要每一個long
和double
變量以前添加volatile
關鍵字.併發
瞭解完原理,來經過一段代碼感覺下volatile
.app
public class Volatile implements Runnable{ //自增變量i public /*volatile*/ int i = 0; @Override public void run() { while (true){ i++; //不斷自增 } } public static void main(String[] args) throws InterruptedException { Volatile vt = new Volatile(); Watcher watcher = new Watcher(); watcher.v = vt; Thread t1 = new Thread(vt); Thread t2 = new Thread(watcher); t1.start(); t2.start(); Thread.sleep(10); //打印 i 和 s System.out.println("Volatile.i = " + vt.i + "\nwatcher.w = " + watcher.monitor); System.exit(0); } } class Watcher implements Runnable{ public Volatile v; public int monitor; @Override public void run() { while (true){ monitor = v.i;//不斷將v.i的值賦給s } } }
// 這是未加volatile修飾的輸出 Volatile.i = 2517483 watcher.w = 1047805 // 打開volatile註釋的輸出結果 Volatile.i = 332754 watcher.w = 333354
第一個輸出中未加volatile
修飾的i
的值和watcher
讀取的值相差太遠,ide
第二個輸出中相差就很少了.而且i
的值比未加volatile
關鍵字的值差不少,說明對volatile
變量的賦值消耗會大一些,不過不用在乎,咱們不多對volatile關鍵字進行不斷自增操做,通常都是做爲狀態或者保證對象完整性,並且volatile
比synchronized
輕量太多了,若是隻爲了保證可見性,volatile必定是最優選.線程
因爲boolean
的賦值是原子性的,因此volatile
布爾變量做爲多線程中止標誌還簡單有效的.code
class Machine{ volatile boolean stopped = false; void stop(){stopped = true;} }
這裏要提到單例對象的雙重檢查鎖,對象完整發布也依賴於happens before
原則,有興趣能夠本身去查閱,這個原則是比較囉嗦,能夠簡單理解爲我知足happens before
,那麼我以前的代碼按順序執行.
public class Singleton { //單例對象 private static Singleton instance = null; //私有化構造器,避免外部經過構造器構造對象 private Singleton(){} //這是靜態工廠方法,用來產生對象 public static Singleton getInstance(){ if(instance ==null){ //同步鎖防止屢次new對象 synchronized (Singleton.class){ //鎖內非空判斷也是爲了防止建立多個對象 if(instance == null){ instance = new Singleton(); } } } return instance; } }
這是一個會產生bug的雙重檢查鎖代碼,instance = new Singleton()
並非一步完成的,他被分爲這幾步:
1.分配對象空間; 2.初始化對象; 3.設置instance指向剛剛分配的地址。
下面圖中,線程A紅色先得到鎖,B黃色後進入.
這種狀況會出現bug,可是因爲volatile
知足happens before
原則,因此會等對象實例化以後再對地址賦值,咱們須要將private static Singleton instance = null;
改爲private static volatile Singleton instance = null;
便可.
其實還有幾種場景,若是想了解更多建議閱讀IBM的技術社區的文章https://www.ibm.com/developerworks/cn/java/j-jtp06197.html