話很少說,直接貼代碼java
class Singleton { private static volatile Singleton instance; private Singleton(){}
//雙重判空 public static Singleton getInstance() { if ( instance == null ) { synchronized (Singleton.class) { if ( instance == null ) { instance = new Singleton(); } } } return instance; } }
這是一個你們耳熟能詳的單例實現,其中有兩個關鍵要點,一是使用雙重檢查鎖定(Double-Checked Locking)來儘可能延遲加鎖時間,以儘可能下降同步開銷;二就是instance實例上加了volatile關鍵字。那麼爲何必定要加volatile關鍵字,volatile又爲咱們作了什麼事情呢?緩存
要了解這個問題,咱們先要搞清楚三個概念:java內存模型(JMM)、happen-before原則、指令重排序。安全
1.java內存模型(Java Memory Model)多線程
Java內存模型中規定了全部的變量都存儲在主內存中,每條線程還有本身的工做內存,線程的工做內存中使用到的變量須要到主內存去拷貝,線程對變量的全部操做(讀取、賦值)都必須在工做內存中進行,而不能直接讀寫主內存中的變量。不一樣線程之間沒法直接訪問對方工做內存中的變量,線程間變量值的傳遞均須要在主內存來完成,線程、主內存和工做內存的交互關係以下圖所示:app
2.happen-before原則性能
Java語言中有一個「先行發生」(happen—before)的規則,它是Java內存模型中定義的兩項操做之間的偏序關係,若是操做A先行發生於操做B,其意思就是說,在發生操做B以前,操做A產生的影響都能被操做B觀察到,「影響」包括修改了內存中共享變量的值、發送了消息、調用了方法等,它與時間上的前後發生基本沒有太大關係。這個原則特別重要,它是判斷數據是否存在競爭、線程是否安全的主要依據。優化
下面是Java內存模型中的八條可保證happen—before的規則,它們無需任何同步器協助就已經存在,能夠在編碼中直接使用。若是兩個操做之間的關係不在此列,而且沒法從下列規則推導出來的話,它們就沒有順序性保障,虛擬機能夠對它們進行隨機地重排序。編碼
3.指令重排序spa
對主存的一次訪問通常花費硬件的數百次時鐘週期。處理器經過緩存(caching)可以從數量級上下降內存延遲的成本,這些緩存爲了性能從新排列待定內存操做的順序。也就是說,程序的讀寫操做不必定會按照它要求處理器的順序執行。線程
JMM經過happens-before法則保證順序執行語義,若是想要讓執行操做B的線程觀察到執行操做A的線程的結果,那麼A和B就必須知足happens-before原則,不然,JVM能夠對它們進行任意排序以提升程序性能。
基於以上三個概念,咱們能夠拆解 instance = new Singleton() 這段代碼:
// thread-A
memory = allocate(); // 1:分配對象的內存空間 ctorInstance(memory); // 2:初始化對象 instance = memory; // 3:設置instance指向剛分配的內存地址
然而,因爲happen-before原則並不能保證這段代碼的順序性,這段代碼可能被編譯器優化爲:
//thread-B
memory = allocate(); // 1:分配對象的內存空間 instance = memory; // 3:設置instance指向剛分配的內存地址 ctorInstance(memory); // 2:初始化對象
在單線程中不管是以哪一種順序執行,都不會對結果有任何影響,然而在多線程下,有可能出現thread-B的執行順序,儘管因爲同步鎖的存在,不會出現兩個線程同時進入instance = new Singleton()的場景,可是若B線程執行完3以後,2尚未執行,CPU就切換時間片,執行一個全新的C線程,將致使C線程拿到一個非空的instance,然而這時候該instance尚未準備好。
而這一切,僅僅須要在instance實例前加上volatile,就能夠完美的解決。
那麼,volatile在例子中到底作了什麼神奇的操做呢?
其一,對於volatile修飾的instance變量,若對instance的寫操做執行在前,那麼該寫操做的結果必定會被馬上刷新到主內存中,以後全部線程對於該instance的全部讀寫操做必然能夠觀察到最新的值,也即:volatile保證了變量的內存可見性
其二,對於volatile修飾的instance變量,將不容許任何與其相關的操做進行指令重排序