關鍵字volatile能夠說是Java虛擬機提供的最輕量級的同步機制,當一個變量定義爲volatile,它具備內存可見性以及禁止指令重排序兩大特性,爲了更好地瞭解volatile關鍵字,咱們能夠先看Java內存模型java
Java內存模型規定了全部的變量都存儲在主內存中,每條線程擁有本身的工做內存,工做內存中保存了被該線程使用到的變量的主內存副本拷貝,線程對變量的全部操做(讀寫)都必須在工做內存中進行,不一樣的線程之間沒法直接訪問對方工做內存的變量。線程、主內存、工做內存關係:面試
以經典的i++爲例,線程A從主內存獲取變量i值放入到工做內存的變量副本,而後在工做內存中將i+1,最後將新值同步到主內存中。從中咱們能夠看出簡單的i++,分了3個步驟,能夠明顯發如今線程A從主內存獲取i值步驟後,可能有其餘線程同步主內存中變量i的值,當線程A想要將i+1結果同步到主內存時就會出現不正確的結果,這是典型的線程不安全。編程
當一個線程修改了共享變量,其餘線程可以當即得知這個修改。Java內存模型經過在變量修改後將新值同步回主內存,volatile變量能保證新值能當即同步到主內存,以及每次使用前當即從主內存刷新(synchronized和final兩個關鍵字也具有)。仍是拿i++爲例,volatile修飾的i能夠確保,從主存中所獲取的變量i必定是最新的。安全
禁止指令重排序,程序執行的順序按照代碼的前後順序執行。
在執行程序時爲了提升性能,編譯器和處理器經常會對指令作重排序。
①.編譯器重排序:編譯器在不改變單線程程序語義的前提下,能夠從新安排語句的執行順序
②.處理器重排序:若是不存在數據依賴性,處理器能夠改變語句對應機器指令的執行順序
從java源代碼到最終實際執行的指令序列,會分別經歷下面三種重排序:
架構
1屬於編譯器重排序,2和3屬於處理器重排序。併發
在某些特定場景中,volatile至關於一個輕量級的sychronize,由於不會引發線程的上下文切換,可是使用volatile必須知足兩個條件:
①.運算結果並不依賴變量的當前值,或者可以確保只有單一的線程修改變量的值
②.變量不須要與其餘的狀態變量共同參與不變約束
兩個使用場景:
異步
性能
public class VolatileTest { private volatile boolean shutdownRequested;複製代碼public void shutdown() { shutdownRequested = true; } public void doWork(){ while (!shutdownRequested) { // 業務邏輯 } } } 複製代碼複製代碼public void shutdown() { shutdownRequested = true; } public void doWork(){ while (!shutdownRequested) { // 業務邏輯 } } } 複製代碼
spa
複製代碼public class Singleton { private volatile static Singleton singleton; public static Singleton getInstance() { if(singleton == null){ synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } } return singleton; } } 複製代碼複製代碼public class Singleton { private volatile static Singleton singleton; public static Singleton getInstance() { if(singleton == null){ synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } } return singleton; } } 複製代碼
從硬件架構上來說,處理器使用寫緩衝區來臨時保存向內存寫入的數據,能夠減小對內存總線的佔用。雖然寫緩衝區有這麼多好處,但此操做僅對它所在的處理器可見,這個特性會對內存操做的執行順序產生重要的影響,因爲操做緩衝區是異步操做因此在外面看來,先寫後讀,仍是先讀後寫,沒有嚴格的固定順序。
線程
volatile修飾的變量相對於普通變量會多出一個lock前綴指令,這個操做至關於一個內存屏障(只有一個CPU訪問內存時,不須要內存屏障;但若是有兩個或更多CPU訪問同一塊內存,且其中有一個在觀測另外一個,就須要內存屏障來保證一致性)。
是否能重排序 | 第二個操做 | |||
第一個操做 | 普通讀 | 普通寫 | volatile讀 | volatile寫 |
普通讀 | LoadStore | |||
普通寫 | StoreStore | |||
volatile讀 | LoadLoad | LoadStore | LoadLoad | LoadStore |
volatile寫 | StoreLoad | StoreStore |
public class VolatileTest { int a = 0; volatile int var1 = 1; volatile int var2 = 2;大體過程:複製代碼void readAndWrite() { int i = var1; //volatile讀 int j = var2; //volatile讀 a = i + i; //普通讀 var1 = i + 1; //volatile寫 var2 = j * 2; //volatile寫 } 複製代碼} 複製代碼void readAndWrite() { int i = var1; //volatile讀 int j = var2; //volatile讀 a = i + i; //普通讀 var1 = i + 1; //volatile寫 var2 = j * 2; //volatile寫 } 複製代碼
1.《深刻理解Java虛擬機》
2.佔小狼——面試必問的volatile,你瞭解多少? 3.《Java併發編程的藝術》