騰訊Java二面:volatile原理分析民逼,你能答出來(lái)嗎

介紹

使用 volatile 修飾的變量是線程共享的全局變量,是輕量級(jí)鎖的一種表現(xiàn)形式涮帘,因?yàn)椴恍枰€程上線文切換和調(diào)度這些操作拼苍,效率杠杠的,但是不能保證原子性调缨,并發(fā)場(chǎng)景下要小心使用疮鲫,比如:多個(gè)線程同時(shí)執(zhí)行 i++ 是有問(wèn)題的。

volatile 的 Demo 代碼:

/**
 * 單例模式(懶漢式)
 * @date:2020 年 7 月 14 日 上午 9:48:24
 */
public class Singleton {
    public static volatile Singleton instance = null;
    private Singleton() {
    }
    public static Singleton getInstance() {
        if (instance == null) {     //代碼 1
            synchronized (instance) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Singleton 對(duì)象是使用 volatile 修飾弦叶,所有線程都可見(jiàn)此對(duì)象俊犯,即有可能被多個(gè)線程同時(shí)訪問(wèn)此對(duì)象,比如有 A 和 B 兩條線程同時(shí)進(jìn)入代碼 1伤哺,如果 B 線程獲取鎖進(jìn)行對(duì)象初始化燕侠,A 線程自旋等待拿鎖,B 線程完成初始化對(duì)象后釋放鎖立莉,然后 A 線程獲取鎖后判斷對(duì)象是否為 null绢彤,為了避免再次初始化對(duì)象節(jié)約了系統(tǒng)開(kāi)銷,所以此處必須使用雙重校驗(yàn) null桃序。

特性及原理

可見(jiàn)性

任意一個(gè)線程修改了 volatile 修飾的變量杖虾,其他線程可以馬上識(shí)別到最新值。實(shí)現(xiàn)可見(jiàn)性的原理如下媒熊。

步驟 1:修改本地內(nèi)存,強(qiáng)制刷回主內(nèi)存坟比。

[

步驟 2:強(qiáng)制讓其他線程的工作內(nèi)存失效過(guò)期芦鳍。

步驟 3:其他線程重新從主內(nèi)存加載最新值。

單個(gè)讀/寫(xiě)具有原子性

單個(gè) volatile 變量的讀/寫(xiě)(比如 vl=l)具有原子性葛账,復(fù)合操作(比如 i++)不具有原子性柠衅,Demo 代碼如下:

public class VolatileFeaturesA {
    private volatile long vol = 0L;

    /**
     * 單個(gè)讀具有原子性
     * @date:2020 年 7 月 14 日 下午 5:02:38
     */
    public long get() {
        return vol;
    }

    /**
     * 單個(gè)寫(xiě)具有原子性
     * @date:2020 年 7 月 14 日 下午 5:01:49
     */
    public void set(long l) {
        vol = l;
    }

    /**
     * 復(fù)合(多個(gè))讀和寫(xiě)不具有原子性
     * @date:2020 年 7 月 14 日 下午 5:02:24
     */
    public void getAndAdd() {
        vol++;
    }

}

互斥性

同一時(shí)刻只允許一個(gè)線程操作 volatile 變量,volatile 修飾的變量在不加鎖的場(chǎng)景下也能實(shí)現(xiàn)有鎖的效果籍琳,類似于互斥鎖菲宴。上面的 VolatileFeaturesA.java 和下面的 VolatileFeaturesB.java 兩個(gè)類實(shí)現(xiàn)的功能是一樣的(除了 getAndAdd 方法)贷祈。

public class VolatileFeaturesB {
    long vol = 0L;

    /**
     * 普通寫(xiě)操作
     * @date:2020 年 7 月 14 日 下午 8:18:34
     * @param l
     */
    public synchronized void set(long l) {  
        vol = l;
    }

    /**
     * 加 1 操作
     * @author songjinzhou
     * @date:2020 年 7 月 14 日 下午 8:28:25
     */
    public void getAndAdd() {
        long temp = get();
        temp += 1L;
        set(temp);
    }

    /**
     * 普通讀操作
     * @date:2020 年 7 月 14 日 下午 8:33:00
     * @return
     */
    public synchronized long get() {
        return vol;
    }
}

部分有序性

JVM 是使用內(nèi)存屏障來(lái)禁止指令重排,從而達(dá)到部分有序性效果喝峦,看看下面的 Demo 代碼分析自然明白為什么只是部分有序:

//a势誊、b 是普通變量,flag 是 volatile 變量
int a = 1;            //代碼 1
int b = 2;            //代碼 2
boolean flag = true;  //代碼 3
int a = 3;            //代碼 4
int b = 4;            //代碼 5

PS:因?yàn)?flag 變量是使用 volatile 修飾谣蠢,則在進(jìn)行指令重排序時(shí)粟耻,不會(huì)把代碼 3 放到代碼 1 和代碼 2 前面,也不會(huì)把代碼 3 放到代碼 4 或者代碼 5 后面眉踱。但是指令重排時(shí)代碼 1 和代碼 2 順序挤忙、代碼 4 和代碼 5 的順序不在禁止重排范圍內(nèi),比如:代碼 2 可能會(huì)被移到代碼 1 之前谈喳。

內(nèi)存屏障類型分為四類册烈。

1. LoadLoadBarriers

指令示例:LoadA —> Loadload —> LoadB

此屏障可以保證 LoadB 和后續(xù)讀指令都可以讀到 LoadA 指令加載的數(shù)據(jù),即讀操作 LoadA 肯定比 LoadB 先執(zhí)行婿禽。

2. StoreStoreBarriers

指令示例:StoreA —> StoreStore —> StoreB

此屏障可以保證 StoreB 和后續(xù)寫(xiě)指令可以操作 StoreA 指令執(zhí)行后的數(shù)據(jù)赏僧,即寫(xiě)操作 StoreA 肯定比 StoreB 先執(zhí)行。

3. LoadStoreBarriers

指令示例: LoadA —> LoadStore —> StoreB

此屏障可以保證 StoreB 和后續(xù)寫(xiě)指令可以讀到 LoadA 指令加載的數(shù)據(jù)谈宛,即讀操作 LoadA 肯定比寫(xiě)操作 StoreB 先執(zhí)行次哈。

4. StoreLoadBarriers

指令示例:StoreA —> StoreLoad —> LoadB

此屏障可以保證 LoadB 和后續(xù)讀指令都可以讀到 StoreA 指令執(zhí)行后的數(shù)據(jù),即寫(xiě)操作 StoreA 肯定比讀操作 LoadB 先執(zhí)行吆录。

實(shí)現(xiàn)有序性的原理:

如果屬性使用了 volatile 修飾窑滞,在編譯的時(shí)候會(huì)在該屬性的前或后插入上面介紹的 4 類內(nèi)存屏障來(lái)禁止指令重排,比如:

  • 在 volatile 寫(xiě)操作的前面插入 StoreStoreBarriers 保證 volatile 寫(xiě)操作之前的普通讀寫(xiě)操作執(zhí)行完畢后再執(zhí)行 volatile 寫(xiě)操作恢筝。
  • 在 volatile 寫(xiě)操作的后面插入 StoreLoadBarriers 保證 volatile 寫(xiě)操作后的數(shù)據(jù)刷新到主內(nèi)存哀卫,保證之后的 volatile 讀寫(xiě)操作能使用最新數(shù)據(jù)(主內(nèi)存)。
  • 在 volatile 讀操作的后面插入 LoadLoadBarriers 和 LoadStoreBarriers 保證 volatile 讀寫(xiě)操作之后的普通讀寫(xiě)操作先把線程本地的變量置為無(wú)效撬槽,再把主內(nèi)存的共享變量更新到本地內(nèi)存此改,之后都使用本地內(nèi)存變量。

volatile 讀操作內(nèi)存屏障:

volatile 寫(xiě)操作內(nèi)存屏障:

3.使用場(chǎng)景

狀態(tài)標(biāo)志侄柔,比如布爾類型狀態(tài)標(biāo)志共啃,作為完成某個(gè)重要事件的標(biāo)識(shí),此標(biāo)識(shí)不能依賴其他任何變量暂题,Demo 代碼如下:

public class Flag {
    //任務(wù)是否完成標(biāo)志移剪,true:已完成,false:未完成
    volatile boolean finishFlag;

    public void finish() {
        finishFlag = true;
    }

    public void doTask() { 
        while (!finishFlag) { 
            //keep do task
        }
    }
}

一次性安全發(fā)布薪者,比如:著名的 double-checked-locking纵苛,demo 代碼上面已貼出。

開(kāi)銷較低的讀,比如:計(jì)算器攻人,Demo 代碼如下取试。

/**
 * 計(jì)數(shù)器
 */
public class Counter {
    private volatile int value;
    //讀操作無(wú)需加鎖,減少同步開(kāi)銷提交性能怀吻,使用 volatile 修飾保證讀操作的可見(jiàn)性瞬浓,每次都可以讀到最新值 
    public int getValue() {
        return value; 
    }
    //寫(xiě)操作使用 synchronized 加鎖,保證原子性
    public synchronized int increment() {
        return value++;
    }
}

最后

覺(jué)得不錯(cuò)的小伙伴記得轉(zhuǎn)發(fā)關(guān)注哦烙博,后續(xù)會(huì)持續(xù)更新精選技術(shù)文章瑟蜈!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市渣窜,隨后出現(xiàn)的幾起案子铺根,更是在濱河造成了極大的恐慌,老刑警劉巖乔宿,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件位迂,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡详瑞,警方通過(guò)查閱死者的電腦和手機(jī)掂林,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)坝橡,“玉大人泻帮,你說(shuō)我怎么就攤上這事〖瓶埽” “怎么了锣杂?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)番宁。 經(jīng)常有香客問(wèn)我元莫,道長(zhǎng),這世上最難降的妖魔是什么蝶押? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任踱蠢,我火速辦了婚禮,結(jié)果婚禮上棋电,老公的妹妹穿的比我還像新娘茎截。我一直安慰自己,他們只是感情好赶盔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布稼虎。 她就那樣靜靜地躺著,像睡著了一般招刨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天沉眶,我揣著相機(jī)與錄音打却,去河邊找鬼。 笑死谎倔,一個(gè)胖子當(dāng)著我的面吹牛柳击,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播片习,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼捌肴,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了藕咏?” 一聲冷哼從身側(cè)響起状知,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎孽查,沒(méi)想到半個(gè)月后饥悴,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡盲再,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年西设,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片答朋。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡贷揽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梦碗,到底是詐尸還是另有隱情,我是刑警寧澤叉弦,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布丐一,位于F島的核電站,受9級(jí)特大地震影響淹冰,放射性物質(zhì)發(fā)生泄漏库车。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一樱拴、第九天 我趴在偏房一處隱蔽的房頂上張望柠衍。 院中可真熱鬧,春花似錦晶乔、人聲如沸珍坊。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)阵漏。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間履怯,已是汗流浹背回还。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留叹洲,地道東北人柠硕。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像运提,于是被迫代替她去往敵國(guó)和親蝗柔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容