volatile关键字到底做了什么?
?話不多說,直接貼代碼
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內存模型中規定了所有的變量都存儲在主內存中,每條線程還有自己的工作內存,線程的工作內存中使用到的變量需要到主內存去拷貝,線程對變量的所有操作(讀取、賦值)都必須在工作內存中進行,而不能直接讀寫主內存中的變量。不同線程之間無法直接訪問對方工作內存中的變量,線程間變量值的傳遞均需要在主內存來完成,線程、主內存和工作內存的交互關系如下圖所示:
2.happen-before原則
Java語言中有一個“先行發生”(happen—before)的規則,它是Java內存模型中定義的兩項操作之間的偏序關系,如果操作A先行發生于操作B,其意思就是說,在發生操作B之前,操作A產生的影響都能被操作B觀察到,“影響”包括修改了內存中共享變量的值、發送了消息、調用了方法等,它與時間上的先后發生基本沒有太大關系。這個原則特別重要,它是判斷數據是否存在競爭、線程是否安全的主要依據。
下面是Java內存模型中的八條可保證happen—before的規則,它們無需任何同步器協助就已經存在,可以在編碼中直接使用。如果兩個操作之間的關系不在此列,并且無法從下列規則推導出來的話,它們就沒有順序性保障,虛擬機可以對它們進行隨機地重排序。
- 單線程happen-before原則:在同一個線程中,書寫在前面的操作happen-before后面的操作。
- 鎖的happen-before原則:同一個鎖的unlock操作happen-before此鎖的lock操作。
- volatile的happen-before原則:對一個volatile變量的寫操作happen-before對此變量的任意操作(當然也包括寫操作了)。
- happen-before的傳遞性原則:如果A操作 happen-before B操作,B操作happen-before C操作,那么A操作happen-before C操作。
- 線程啟動的happen-before原則:同一個線程的start方法happen-before此線程的其它方法。
- 線程中斷的happen-before原則:對線程interrupt方法的調用happen-before被中斷線程的檢測到中斷發送的代碼。
- 線程終結的happen-before原則:線程中的所有操作都happen-before線程的終止檢測。
- 對象創建的happen-before原則:一個對象的初始化完成先于他的finalize方法調用。
?
3.指令重排序
對主存的一次訪問一般花費硬件的數百次時鐘周期。處理器通過緩存(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變量,將不允許任何與其相關的操作進行指令重排序
https://www.cnblogs.com/Jasonchan1994/p/10696930.html
總結
以上是生活随笔為你收集整理的volatile关键字到底做了什么?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2018汇总数据结构算法篇
- 下一篇: NLP热门词汇解读