目錄java
volatile是java語言中的關鍵字,用來修飾會被多線程訪問的共享變量,是JVM提供的輕量級的同步機制,相比同步代碼塊或者重入鎖有更好的性能。它主要有兩重語義,一是保證多個線程對共享變量訪問的可見性,二防止指令重排序。緩存
public class TestVolatile { public static void main(String[] args) throws InterruptedException { ThreadDemo threadDemo = new ThreadDemo(); new Thread(threadDemo).start(); threadDemo.flag = false; System.out.println("已將flag置爲" + threadDemo.flag); } static class ThreadDemo implements Runnable { boolean flag = true; @Override public void run() { System.out.println("Flag=" + flag); } } }
當你屢次執行代碼時,有必定概率會出現這種結果
安全
在主線程將子線程實例的flag置爲false後,子線程中的flag居然仍是true。這是怎麼回事?這就是多線程的內存可見性問題。對於一個沒有volatile修飾的的共享變量,當一個線程對其進行了修改,另外一線程並不必定能立刻看見這個被修改後的值。爲何會出現這種狀況呢?這就要從java的內存模型談起。多線程
java的內存模型定義了線程和主內存之間的抽象關係,它的內容主要包括:併發
線程,主內存,工做內存三者的交互關係如圖所示
app
看看JMM模型會給咱們在多線程環境下的讀寫帶來什麼樣的問題。jvm
這時就出現了共享變量在多線程環境下的可見性問題。若是把線程的工做內存看成主內存的緩存,這個問題的本質就在於如何解決緩存失效問題。那麼JMM中是如何解決可見性問題的?這就不得不提到happens-before規則。ide
happens-before規則又叫先行發生規則。它定義了java內存模型中兩項操做的偏序關係,更確切的說,它定義了操做可見性之間的偏序關係。好比A操做 happens-before B操做,並不意味這A操做必定在B操做以前,而是A操做的影響能被操做B觀察到,這個影響包括改變了內存中共享變量的值,發送消息等。那麼JMM定義了哪些happens-before規則?工具
當一個變量被修飾爲volatile後,對其的讀寫就會顯得比較特別性能
1.寫一個volatile變量時,JMM首先修改工做內存中的變量值,並刷新到主內存中
如圖所示
2.讀一個變量時,JMM會把該線程對應的本地內存置爲無效,並從主內存中讀取共享變量。
如圖所示
對volatile變量的讀寫,能夠說都是直接對主內存進行的操做,這樣雖然會犧牲一些性能,可是解決了「緩存一致性問題」,使得變量在多線程間的可見性獲得了很好的保證。
爲了優化程序性能,編譯器和處理器會對java編譯後的字節碼和機器指令進行重排序,通俗的說代碼的執行順序和咱們在程序中定義的順序會有些不一樣,只要不改變單線程環境下的執行結果就行。可是在多線程環境下,這麼作卻可能出現併發問題。好比下面的例子。
運行這段代碼咱們可能會獲得一個匪夷所思的結果:咱們得到的單例對象是未初始化的。爲何會出現這種狀況?由於指令重排。首先要明確一點,同步代碼塊中的代碼也是可以被指令重排的。而後來看問題的關鍵
INSTANCE = new Singleton();
雖然在代碼中只有一行,編譯出的字節碼指令能夠用以下三行表示
if (INSTANCE == null)
那麼這時候有意思的事情就發生了:雖然INSTANCE指向了一個未被初始化的對象,可是它確實不爲null了,因此這個判斷會返回false,以後它將return一個未被初始化的單例對象!整個過程的執行流程以下圖所示
因爲重排序是編譯器和CPU自動進行的,那麼有什麼辦法能禁止這種重排序操做嗎?很簡單,給
INSTANCE變量加個volatile關鍵字就行,這樣編譯器就會根據必定的規則禁止對volatile變量的讀寫操做重排序了。而編譯出的字節碼,也會在合適的地方插入內存屏障,好比volatile寫操做以前和以後會分別插入一個StoreStore屏障和StoreLoad屏障,禁止CPU對指令的重排序越過這些屏障。
對volatile變量的讀寫具備原子性,可是其餘操做並不必定具備原子性,一個簡單的例子就是i++。因爲該操做並不具備原子性,故而即便該變量被volatile修飾,多線程環境下也不能保證線程安全。
volatile是jvm提供的輕量級同步工具。被volatile修飾的共享變量在多線程環境下能夠得到可見行保證。其次它還能禁止指令重排。因爲對volatile的寫-讀與鎖的釋放-獲取具備相同的內存語義,故某些時候能夠代替鎖來得到更好的性能。可是和鎖不同,它不能保證任什麼時候候都是線程安全的。