有多少人面試栽到Volatile上灸撰?面試問題都總結(jié)到這兒了

Volatile關(guān)鍵字

volatile 是Java虛擬機(jī)提供的 輕量級(jí) 的同步機(jī)制.何為 輕量級(jí) 呢哨啃,這要相對(duì)于 synchronized 來說号阿。Volatile有如下三個(gè)特點(diǎn)迁沫。

要搞清楚上面列舉的名詞 可見性 原子性 指令重排 的含義我們需要首先弄清楚JMM(Java內(nèi)存模型是怎么回事)

JMM規(guī)定了內(nèi)存主要?jiǎng)澐譃?主內(nèi)存工作內(nèi)存 兩種芦瘾。此處的主內(nèi)存和工作內(nèi)存跟JVM內(nèi)存劃分(堆、棧集畅、方法區(qū))是在不同的層次上進(jìn)行的旅急,如果非要對(duì)應(yīng)起來,主內(nèi)存對(duì)應(yīng)的是Java堆中的對(duì)象實(shí)例部分牡整,工作內(nèi)存對(duì)應(yīng)的是棧中的部分區(qū)域,從更底層的來說溺拱,主內(nèi)存對(duì)應(yīng)的是硬件的物理內(nèi)存逃贝,工作內(nèi)存對(duì)應(yīng)的是寄存器和高速緩存.

image

JVM在設(shè)計(jì)時(shí)候考慮到,如果JAVA線程每次讀取和寫入變量都直接操作主內(nèi)存迫摔,對(duì)性能影響比較大沐扳,所以每條線程擁有各自的工作內(nèi)存,工作內(nèi)存中的變量是主內(nèi)存中的一份 拷貝 句占,線程對(duì)變量的讀取和寫入沪摄,直接在工作內(nèi)存中操作,而不能直接去操作主內(nèi)存中的變量。但是這樣就會(huì)出現(xiàn)一個(gè)問題杨拐,當(dāng)一個(gè)線程修改了自己工作內(nèi)存中變量祈餐,對(duì)其他線程是不可見的,會(huì)導(dǎo)致線程不安全的問題哄陶。因?yàn)镴MM制定了一套標(biāo)準(zhǔn)來保證開發(fā)者在編寫多線程程序的時(shí)候帆阳,能夠控制什么時(shí)候內(nèi)存會(huì)被同步給其他線程。

各個(gè)線程對(duì)主內(nèi)存中共享變量的操作都是各個(gè)線程各自拷貝到自己的工作內(nèi)存進(jìn)行操作后再寫回主內(nèi)存中的屋吨。

這就可能存在一個(gè)線程A修改了共享變量X的值但還未寫回主內(nèi)存時(shí)蜒谤,另一個(gè)線程B又對(duì)準(zhǔn)內(nèi)存中同一個(gè)共享變量X進(jìn)行操作,但此時(shí)A線程工作內(nèi)存中共享變量X對(duì)線程B來說并不是可見至扰,這種工作內(nèi)存與主內(nèi)存同步存在延遲現(xiàn)象就造成了可見性問題鳍徽。

通過代碼來看下可見性的問題

package com.dpb.spring.aop.demo;

import java.util.concurrent.TimeUnit;

/**
 * 可見性問題分析
 */
public class VolatileDemo1 {
    public static void main(String[] args){
        final MyData myData = new MyData();
        // 開啟一個(gè)新的線程
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "開始了...");
            try{TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}
            // 在子線程中修改了變量的信息  修改的本線程在工作內(nèi)存中的數(shù)據(jù)
            myData.addTo60();
            System.out.println(Thread.currentThread().getName() + "更新后的數(shù)據(jù)是:"+myData.number);
        },"BBB").start();
        // 因?yàn)樵谄渌€程中修改的信息主線程的工作內(nèi)存中的數(shù)據(jù)并沒有改變所以此時(shí)number還是為0
        while(myData.number == 0){
            // 會(huì)一直卡在此處
            //System.out.println("1111");
        }
        System.out.println(Thread.currentThread().getName()+"\t number =  " + myData.number);
    }
}

class MyData{
    // 沒有用volatile來修飾
    int number = 0;

    public void addTo60(){
        this.number = 60;
    }

}

效果如下:

image

通過 volatile 來解決此問題

image
image

我們可以發(fā)現(xiàn)當(dāng)變量被 volatile 修飾的時(shí)候,在子線程的工作內(nèi)存中的變量被修改后其他線程中對(duì)應(yīng)的變量是可以立馬知道的敢课。這就是我們講的可見性

原子性是 不可分割 阶祭, 完整性 ,也即某個(gè)線程正在做某個(gè)具體業(yè)務(wù)時(shí)翎猛,中間不可以被加塞或者分割,需要整體完成胖翰,要么同時(shí)成功,要么同時(shí)失敗.

volatile是 不支持 原子性的,接下來我們可以驗(yàn)證下切厘。


import java.util.concurrent.TimeUnit;

/**
 * 可見性問題分析
 */
public class VolatileDemo2 {
    public static void main(String[] args){
        final MyData2 myData = new MyData2();
        for (int i = 1; i <= 20 ; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000 ; j++) {
                    myData.addPlusPlus();
                }
            },String.valueOf(i)).start();
        }
        // 等待子線程執(zhí)行完成
        while(Thread.activeCount() > 2){
            Thread.yield();
        }
        // 在主線程中獲取統(tǒng)計(jì)的信息值
        System.out.println(Thread.currentThread().getName()+"\t finnally number value: "+myData.number);
    }
}

class MyData2{
   // 操作的變量被volatile修飾了
    volatile int number = 0;

    public void addPlusPlus(){
        number++;
    }

}

執(zhí)行的效果

image

根據(jù)正常的邏輯在開啟的20個(gè)子線程萨咳,每個(gè)執(zhí)行1000遍累加,得到的結(jié)果應(yīng)該是20000疫稿,但是我們發(fā)現(xiàn)運(yùn)行的結(jié)果大概率會(huì)比我們期望的要小培他,而且變量也已經(jīng)被volatile修飾了。說明并沒有滿足我們要求的原子性遗座。這種情況下我們要保證操作的原子性舀凛,我們有兩個(gè)選擇

  1. 通過synchronized來實(shí)現(xiàn)

  2. 通過 JUC 下的 AtomicInteger 來實(shí)現(xiàn)

synchronized的實(shí)現(xiàn)是重量級(jí)的,影響并發(fā)的效率途蒋,所以我們通過AtomicInteger來實(shí)現(xiàn)猛遍。

package com.dpb.spring.aop.demo;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 可見性問題分析
 */
public class VolatileDemo2 {
    public static void main(String[] args){
        final MyData2 myData = new MyData2();
        for (int i = 1; i <= 20 ; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000 ; j++) {
                    myData.addPlusPlus();
                    myData.addAtomicPlus();
                }
            },String.valueOf(i)).start();
        }
        // 等待子線程執(zhí)行完成
        while(Thread.activeCount() > 2){
            Thread.yield();
        }
        // 在主線程中獲取統(tǒng)計(jì)的信息值
        System.out.println(Thread.currentThread().getName()+"\t finnally number value: "+myData.number);
        System.out.println(Thread.currentThread().getName()+"\t finnally number value: "+myData.atomicInteger.get());
    }
}

class MyData2{
   // 操作的變量被volatile修飾了
    volatile int number = 0;
    // AtomicInteger 來保證操作的原子性
    AtomicInteger atomicInteger = new AtomicInteger();

    public  void addPlusPlus(){
        number++;
    }

    public void addAtomicPlus(){
        atomicInteger.getAndIncrement();
    }

}

效果:

image

注意 :通過效果發(fā)現(xiàn) AtomicInteger 在多線程環(huán)境下處理的數(shù)據(jù)和我們期望的結(jié)果是一致的都是 20000 .說明實(shí)現(xiàn)的操作的原子性。

有序性

計(jì)算機(jī)在執(zhí)行程序時(shí)号坡,為了提高性能懊烤,編譯器和處理器常常會(huì)對(duì)指令做重排,一般分以下3種:

image

1.單線程環(huán)境里面確保程序最終執(zhí)行結(jié)果和代碼順序執(zhí)行的結(jié)果一致宽堆。

2.處理器在進(jìn)行重排序時(shí)必須考慮指令之間的數(shù)據(jù)依賴性腌紧。

3.多線程環(huán)境中線程交替執(zhí)行,由于編譯器優(yōu)化重排的存在畜隶,兩個(gè)線程中使用的變量能否保證一致性是無法確定的壁肋,結(jié)果無法預(yù)測(cè)号胚。

案例代碼

package com.dpb.spring.aop.demo;

public class SortDemo {
    int a = 0;
    boolean flag = false;

    public void fun1(){
        a = 1;  // 語句1
        flag = true; // 語句2
    }

    public void fun2(){
        if(flag){
            a = a + 5; // 語句3
            System.out.println("a = " + a );
        }
    }
}

注意: 在多線程環(huán)境中線程交替執(zhí)行,由于編譯器優(yōu)化重排的存在浸遗,兩個(gè)線程中使用的變量能否保證一致性是無法確定的猫胁,結(jié)果無法預(yù)測(cè)。

指令重排小結(jié):

volatile實(shí)現(xiàn)禁止指令重排優(yōu)化乙帮,從而避免多線程環(huán)境下程序出現(xiàn)亂序執(zhí)行的現(xiàn)象杜漠。

先了解一個(gè)概念, 內(nèi)存屏障 又稱 內(nèi)存柵欄 察净,是一個(gè)CPU指令驾茴,它的作用有兩個(gè):

  1. 是保證特定操作的執(zhí)行順序

  2. 是保證某些變量的內(nèi)存可見性(利用該特性實(shí)現(xiàn)volatile的內(nèi)存可見性)

由于編譯器和處理器都能執(zhí)行指令重排優(yōu)化。如果在指令間插入一條Memory Barrier則告訴編譯器和CPU氢卡,不管什么指令都不能和這條Memory Barrier指令重新排序锈至,也就是說通過插入內(nèi)存屏障禁止在內(nèi)存屏障前后的指令執(zhí)行重排序優(yōu)化。內(nèi)存屏障另外一個(gè)作用是強(qiáng)制刷出各種CPU的緩存數(shù)據(jù)译秦,因此任何CPU上的線程都能讀取到這些數(shù)據(jù)的最新版本峡捡。

線程安全的總結(jié):

  1. 工作內(nèi)存和主內(nèi)存同步延遲現(xiàn)象導(dǎo)致的 可見性問題 ,可以使用synchronized或volatile關(guān)鍵字解決筑悴,他們都可以使一個(gè)線程修改后的變量立即對(duì)其他線程可見们拙。

  2. 對(duì)于指令重排導(dǎo)致的 可見性問題有序性問題 ,可以利用volatile關(guān)鍵字解決阁吝,因?yàn)関olatile的另外一個(gè)作用就是禁止重排序優(yōu)化砚婆。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市突勇,隨后出現(xiàn)的幾起案子装盯,更是在濱河造成了極大的恐慌,老刑警劉巖甲馋,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件埂奈,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡定躏,警方通過查閱死者的電腦和手機(jī)账磺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痊远,“玉大人绑谣,你說我怎么就攤上這事∞忠” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵幌衣,是天一觀的道長矾削。 經(jīng)常有香客問我壤玫,道長,這世上最難降的妖魔是什么哼凯? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任欲间,我火速辦了婚禮,結(jié)果婚禮上断部,老公的妹妹穿的比我還像新娘猎贴。我一直安慰自己,他們只是感情好蝴光,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布她渴。 她就那樣靜靜地躺著,像睡著了一般蔑祟。 火紅的嫁衣襯著肌膚如雪趁耗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天疆虚,我揣著相機(jī)與錄音苛败,去河邊找鬼。 笑死径簿,一個(gè)胖子當(dāng)著我的面吹牛罢屈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播篇亭,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼缠捌,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了暗赶?” 一聲冷哼從身側(cè)響起鄙币,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蹂随,沒想到半個(gè)月后十嘿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡岳锁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年绩衷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片激率。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡咳燕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出乒躺,到底是詐尸還是另有隱情招盲,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布嘉冒,位于F島的核電站曹货,受9級(jí)特大地震影響咆繁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜顶籽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一玩般、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧礼饱,春花似錦坏为、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至镰吆,卻和暖如春帘撰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背万皿。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來泰國打工摧找, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人牢硅。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓蹬耘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親减余。 傳聞我的和親對(duì)象是個(gè)殘疾皇子综苔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348