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;} }?? 這是一個(gè)大家耳熟能詳?shù)膯卫龑?shí)現(xiàn),其中有兩個(gè)關(guān)鍵要點(diǎn),一是使用雙重檢查鎖定(Double-Checked Locking)來盡量延遲加鎖時(shí)間,以盡量降低同步開銷;二就是instance實(shí)例上加了volatile關(guān)鍵字。那么為什么一定要加volatile關(guān)鍵字,volatile又為我們做了什么事情呢?
?? 要了解這個(gè)問題,我們先要搞清楚三個(gè)概念:java內(nèi)存模型(JMM)、happen-before原則、指令重排序。
1.java內(nèi)存模型(Java Memory Model)
Java內(nèi)存模型中規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存中,每條線程還有自己的工作內(nèi)存,線程的工作內(nèi)存中使用到的變量需要到主內(nèi)存去拷貝,線程對(duì)變量的所有操作(讀取、賦值)都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫主內(nèi)存中的變量。不同線程之間無法直接訪問對(duì)方工作內(nèi)存中的變量,線程間變量值的傳遞均需要在主內(nèi)存來完成,線程、主內(nèi)存和工作內(nèi)存的交互關(guān)系如下圖所示:
2.happen-before原則
Java語言中有一個(gè)“先行發(fā)生”(happen—before)的規(guī)則,它是Java內(nèi)存模型中定義的兩項(xiàng)操作之間的偏序關(guān)系,如果操作A先行發(fā)生于操作B,其意思就是說,在發(fā)生操作B之前,操作A產(chǎn)生的影響都能被操作B觀察到,“影響”包括修改了內(nèi)存中共享變量的值、發(fā)送了消息、調(diào)用了方法等,它與時(shí)間上的先后發(fā)生基本沒有太大關(guān)系。這個(gè)原則特別重要,它是判斷數(shù)據(jù)是否存在競(jìng)爭(zhēng)、線程是否安全的主要依據(jù)。
下面是Java內(nèi)存模型中的八條可保證happen—before的規(guī)則,它們無需任何同步器協(xié)助就已經(jīng)存在,可以在編碼中直接使用。如果兩個(gè)操作之間的關(guān)系不在此列,并且無法從下列規(guī)則推導(dǎo)出來的話,它們就沒有順序性保障,虛擬機(jī)可以對(duì)它們進(jìn)行隨機(jī)地重排序。
- 單線程happen-before原則:在同一個(gè)線程中,書寫在前面的操作happen-before后面的操作。
- 鎖的happen-before原則:同一個(gè)鎖的unlock操作happen-before此鎖的lock操作。
- volatile的happen-before原則:對(duì)一個(gè)volatile變量的寫操作happen-before對(duì)此變量的任意操作(當(dāng)然也包括寫操作了)。
- happen-before的傳遞性原則:如果A操作 happen-before B操作,B操作happen-before C操作,那么A操作happen-before C操作。
- 線程啟動(dòng)的happen-before原則:同一個(gè)線程的start方法happen-before此線程的其它方法。
- 線程中斷的happen-before原則:對(duì)線程interrupt方法的調(diào)用happen-before被中斷線程的檢測(cè)到中斷發(fā)送的代碼。
- 線程終結(jié)的happen-before原則:線程中的所有操作都happen-before線程的終止檢測(cè)。
- 對(duì)象創(chuàng)建的happen-before原則:一個(gè)對(duì)象的初始化完成先于他的finalize方法調(diào)用。
?
3.指令重排序
對(duì)主存的一次訪問一般花費(fèi)硬件的數(shù)百次時(shí)鐘周期。處理器通過緩存(caching)能夠從數(shù)量級(jí)上降低內(nèi)存延遲的成本,這些緩存為了性能重新排列待定內(nèi)存操作的順序。也就是說,程序的讀寫操作不一定會(huì)按照它要求處理器的順序執(zhí)行。
JMM通過happens-before法則保證順序執(zhí)行語義,如果想要讓執(zhí)行操作B的線程觀察到執(zhí)行操作A的線程的結(jié)果,那么A和B就必須滿足happens-before原則,否則,JVM可以對(duì)它們進(jìn)行任意排序以提高程序性能。
?
? ? 基于以上三個(gè)概念,我們可以拆解 instance =?new?Singleton() 這段代碼:
?
// thread-A memory = allocate(); // 1:分配對(duì)象的內(nèi)存空間 ctorInstance(memory); // 2:初始化對(duì)象 instance = memory; // 3:設(shè)置instance指向剛分配的內(nèi)存地址?
?? 然而,由于happen-before原則并不能保證這段代碼的順序性,這段代碼可能被編譯器優(yōu)化為:
//thread-B memory = allocate(); // 1:分配對(duì)象的內(nèi)存空間 instance = memory; // 3:設(shè)置instance指向剛分配的內(nèi)存地址 ctorInstance(memory); // 2:初始化對(duì)象?? 在單線程中不論是以哪種順序執(zhí)行,都不會(huì)對(duì)結(jié)果有任何影響,然而在多線程下,有可能出現(xiàn)thread-B的執(zhí)行順序,盡管由于同步鎖的存在,不會(huì)出現(xiàn)兩個(gè)線程同時(shí)進(jìn)入instance =?new?Singleton()的場(chǎng)景,但是若B線程執(zhí)行完3之后,2還沒有執(zhí)行,CPU就切換時(shí)間片,執(zhí)行一個(gè)全新的C線程,將導(dǎo)致C線程拿到一個(gè)非空的instance,然而這時(shí)候該instance還沒有準(zhǔn)備好。
?? 而這一切,僅僅需要在instance實(shí)例前加上volatile,就可以完美的解決。
?? 那么,volatile在例子中到底做了什么神奇的操作呢?
其一,對(duì)于volatile修飾的instance變量,若對(duì)instance的寫操作執(zhí)行在前,那么該寫操作的結(jié)果一定會(huì)被立刻刷新到主內(nèi)存中,之后所有線程對(duì)于該instance的所有讀寫操作必然可以觀察到最新的值,也即:volatile保證了變量的內(nèi)存可見性
?????? 其二,對(duì)于volatile修飾的instance變量,將不允許任何與其相關(guān)的操作進(jìn)行指令重排序
https://www.cnblogs.com/Jasonchan1994/p/10696930.html
總結(jié)
以上是生活随笔為你收集整理的volatile关键字到底做了什么?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2018汇总数据结构算法篇
- 下一篇: NLP热门词汇解读