簡(jiǎn)單理解volatile關(guān)鍵字

什么是volatile衙四?

volatile首先這個(gè)詞的音標(biāo):[?v?l?ta?l],翻譯過(guò)來(lái)是易變的侵贵,不穩(wěn)定的意思届搁。

  1. 它是一個(gè)關(guān)鍵字,翻譯過(guò)來(lái)是易改變的意思窍育。
  2. 我們經(jīng)常用它來(lái)修飾成員變量卡睦。
  3. 被它修飾的變量具備兩種特性:第一:保證這個(gè)變量的可見(jiàn)性,但不具有原子性漱抓。第二:禁止進(jìn)行指令重排序表锻。
  4. 當(dāng)一個(gè)變量被 volatile 修飾時(shí),任何線程對(duì)它的寫(xiě)操作都會(huì)立即刷新到主內(nèi)存中乞娄,并且會(huì)強(qiáng)制讓緩存了該變量的線程中的數(shù)據(jù)清空瞬逊,必須從主內(nèi)存重新讀取最新數(shù)據(jù)。并不是讓線程直接從主內(nèi)存中獲取數(shù)據(jù)仪或,依然需要將變量拷貝到工作內(nèi)存中确镊。

volatile的原理是什么?

  1. 既然它是關(guān)鍵字范删,那就表明它跟syn一樣蕾域,沒(méi)有可見(jiàn)的源碼,都是jvm層面了到旦,其中涉及到了內(nèi)存模型也就是JMM(Java Memory Model)旨巷。
  2. 內(nèi)存模型是什么?是Java虛擬機(jī)規(guī)范中定義了一種Java內(nèi)存模型來(lái)屏蔽各個(gè)硬件平臺(tái)和操作系統(tǒng)的內(nèi)存訪問(wèn)差異添忘,以實(shí)現(xiàn)讓Java程序在各種平臺(tái)下都能達(dá)到一致的內(nèi)存訪問(wèn)效果采呐。
  3. 為了獲得較好的執(zhí)行性能,Java內(nèi)存模型并沒(méi)有限制執(zhí)行引擎使用處理器的寄存器或者高速緩存來(lái)提升指令執(zhí)行速度搁骑,也沒(méi)有限制編譯器對(duì)指令進(jìn)行重排序斧吐。也就是說(shuō)又固,在java內(nèi)存模型中,也會(huì)存在緩存一致性問(wèn)題和指令重排序的問(wèn)題会通。
  4. Java內(nèi)存模型規(guī)定所有的變量都是存在主存當(dāng)中(類似于物理內(nèi)存)口予,每個(gè)線程都有自己的工作內(nèi)存(類似于高速緩存)。線程對(duì)變量的所有操作都必須在工作內(nèi)存中進(jìn)行涕侈,而不能直接對(duì)主存進(jìn)行操作沪停。并且每個(gè)線程不能訪問(wèn)其他線程的工作內(nèi)存。也有的人說(shuō)主內(nèi)存可以簡(jiǎn)單認(rèn)為是堆內(nèi)存裳涛,而工作內(nèi)存則可以認(rèn)為是棧內(nèi)存木张。

舉個(gè)例子:

int i = 10;

執(zhí)行線程必須先在自己的工作線程中對(duì)變量i所在的緩存行進(jìn)行賦值操作,然后再寫(xiě)入主存當(dāng)中端三。而不是直接將數(shù)值10寫(xiě)入主存當(dāng)中舷礼。

原子性

即一個(gè)操作或者多個(gè)操作 要么全部執(zhí)行并且執(zhí)行的過(guò)程不會(huì)被任何因素打斷,要么就都不執(zhí)行郊闯。

一個(gè)很經(jīng)典的例子就是銀行賬戶轉(zhuǎn)賬問(wèn)題:

比如從賬戶A向賬戶B轉(zhuǎn)1000元妻献,那么必然包括2個(gè)操作:從賬戶A減去1000元,往賬戶B加上1000元团赁。如果這2個(gè)操作不具備原子性育拨,會(huì)造成什么樣的后果。假如從賬戶A減去1000元之后欢摄,操作突然中止熬丧。然后又從B取出了500元,取出500元之后怀挠,再執(zhí)行 往賬戶B加上1000元 的操作析蝴。這樣就會(huì)導(dǎo)致賬戶A雖然減去了1000元,但是賬戶B沒(méi)有收到這個(gè)轉(zhuǎn)過(guò)來(lái)的1000元绿淋。

volatile關(guān)鍵字并不能保證數(shù)據(jù)的原子性闷畸,所以說(shuō)它是線程安全的是錯(cuò)誤的,看似簡(jiǎn)單的 i++ 操作在多線程的環(huán)境下吞滞,是不安全的佑菩,但是可以通過(guò)加鎖來(lái)保證原子性。

i ++ 操作在計(jì)算機(jī)中其實(shí)進(jìn)行了三個(gè)步驟:

  1. cpu從主內(nèi)存中讀取 i 變量的值冯吓,并且復(fù)制到工作內(nèi)存中倘待。
  2. 在工作內(nèi)存中+1疮跑。
  3. 將+1后的結(jié)果刷回主內(nèi)存中组贺。

舉例:

public class VolatileTest implements Runnable {

    //使用 volatile 修飾基本數(shù)據(jù)內(nèi)存不能保證原子性
    private static volatile int count = 0;

    public void run() {
        for (int i = 0; i < 10000; i++) {
            count++;
        }
        System.out.println(Thread.currentThread().getName() + " : " + count);
    }

    public static void main(String[] args) {
        VolatileTest volatiletest = new VolatileTest();
        Thread t1 = new Thread(volatiletest, "t1");
        Thread t2 = new Thread(volatiletest, "t2");
        t1.start();
        t2.start();
    }
}

運(yùn)行結(jié)果:幾乎每次都不一樣,這是因?yàn)樽婺铮恿藇olatile 每次都會(huì)去主內(nèi)存中拿最新的值失尖“⊙伲可以通過(guò)給run方法加鎖方式或者利用AtomicInteger來(lái)替換int來(lái)實(shí)現(xiàn)同步。

可見(jiàn)性

可見(jiàn)性是指當(dāng)多個(gè)線程訪問(wèn)同一個(gè)變量時(shí)掀潮,一個(gè)線程修改了這個(gè)變量的值菇夸,其他線程能夠立即看得到修改的值。
舉例:

// 線程1執(zhí)行的代碼
int i = 0;
i = 10;
 
// 線程2執(zhí)行的代碼
j = i;

上面的分析可知仪吧,當(dāng)線程1執(zhí)行 i =10這句時(shí)庄新,會(huì)先把i的初始值加載到線程1的高速緩存中,然后賦值為10薯鼠,那么在線程1的高速緩存當(dāng)中i的值變?yōu)?0了择诈,卻沒(méi)有立即寫(xiě)入到主存當(dāng)中。此時(shí)線程2執(zhí)行 j = i出皇,它會(huì)先去主存讀取i的值并加載到線程2的緩存當(dāng)中羞芍,注意此時(shí)內(nèi)存當(dāng)中i的值還是0,那么就會(huì)使得j的值為0郊艘,而不是10荷科。這就是可見(jiàn)性問(wèn)題,線程1對(duì)變量i修改了之后纱注,線程2沒(méi)有立即看到線程1修改的值畏浆。

另外,通過(guò)synchronized和Lock也能夠保證可見(jiàn)性奈附,synchronized和Lock能保證同一時(shí)刻只有一個(gè)線程獲取鎖然后執(zhí)行同步代碼全度,并且在釋放鎖之前會(huì)將對(duì)變量的修改刷新到主存當(dāng)中。因此可以保證可見(jiàn)性斥滤。

有序性和指令重排序(Instruction Reorder)

有序性即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行将鸵。(happens-before原則以后再補(bǔ)充。)

舉例:

int i = 0;              
boolean flag = false;
i = 1;                //語(yǔ)句1  
flag = true;          //語(yǔ)句2

上面代碼定義了一個(gè)int型變量佑颇,定義了一個(gè)boolean類型變量顶掉,然后分別對(duì)兩個(gè)變量進(jìn)行賦值操作。從代碼順序上看挑胸,語(yǔ)句1是在語(yǔ)句2前面的痒筒,那么JVM在真正執(zhí)行這段代碼的時(shí)候會(huì)保證語(yǔ)句1一定會(huì)在語(yǔ)句2前面執(zhí)行嗎?不一定茬贵,這里可能會(huì)發(fā)生指令重排序簿透。

指令重排序,一般來(lái)說(shuō)解藻,處理器為了提高程序運(yùn)行效率老充,可能會(huì)對(duì)輸入代碼進(jìn)行優(yōu)化,它不保證程序中各個(gè)語(yǔ)句的執(zhí)行先后順序同代碼中的順序一致螟左,但是它會(huì)保證程序最終執(zhí)行結(jié)果和代碼順序執(zhí)行的結(jié)果是一致的啡浊。

舉例:

int a = 10;    //語(yǔ)句1
int b = 2;    //語(yǔ)句2
int c = a + b;    //語(yǔ)句3

上面代碼可能發(fā)生的順序?yàn)椋赫Z(yǔ)句2 -->語(yǔ)句1-->語(yǔ)句3觅够,但不可能是語(yǔ)句2 -->語(yǔ)句3-->語(yǔ)句1,因?yàn)樘幚砥髟谶M(jìn)行重排序時(shí)是會(huì)考慮指令之間的數(shù)據(jù)依賴性巷嚣,如果一個(gè)指令2必須用到指令1的結(jié)果喘先,那么處理器會(huì)保證指令1會(huì)在指令2之前執(zhí)行。

但在多線程的情況下:

//線程1:
context = loadContext();   //語(yǔ)句1
inited = true;             //語(yǔ)句2
 
//線程2:
while(!inited ){
  sleep()
}
doSomethingwithconfig(context);

上面代碼中廷粒,由于語(yǔ)句1和語(yǔ)句2沒(méi)有數(shù)據(jù)依賴性窘拯,因此可能會(huì)被重排序。假如發(fā)生了重排序坝茎,在線程1執(zhí)行過(guò)程中先執(zhí)行語(yǔ)句2树枫,而此是線程2會(huì)以為初始化工作已經(jīng)完成,那么就會(huì)跳出while循環(huán)景东,去執(zhí)行doSomethingwithconfig(context)方法砂轻,而此時(shí)context并沒(méi)有被初始化,就會(huì)導(dǎo)致程序出錯(cuò)斤吐。

從上面可以看出搔涝,指令重排序不會(huì)影響單個(gè)線程的執(zhí)行,但是會(huì)影響到線程并發(fā)執(zhí)行的正確性和措。也就是說(shuō)庄呈,要想并發(fā)程序正確地執(zhí)行,必須要保證原子性派阱、可見(jiàn)性以及有序性诬留。只要有一個(gè)沒(méi)有被保證,就有可能會(huì)導(dǎo)致程序運(yùn)行不正確贫母。

什么場(chǎng)景下會(huì)用到volatile文兑?

  1. 對(duì)變量的寫(xiě)操作不依賴于當(dāng)前值。
  2. 該變量沒(méi)有包含在具有其他變量的不變式中腺劣。
  3. 只有一個(gè)線程寫(xiě)绿贞,多個(gè)線程讀的情況下使用比較頻繁。

哪里用到了volatile橘原?

1. 狀態(tài)標(biāo)記量

volatile boolean flag = false;
while(!flag){
    doSomething();
} 
public void setFlag() {
    flag = true;
}

多線程

volatile boolean inited = false;
//線程1:
context = loadContext();  
inited = true;            
 
//線程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);

2. double check

雙重懶加載的單例模式
這里的 volatile 關(guān)鍵字主要是為了防止指令重排籍铁。
如果不用 ,singleton = new Singleton();趾断,這段代碼其實(shí)是分為三步:

  1. 分配內(nèi)存空間拒名。
  2. 初始化對(duì)象。
  3. 將 singleton 對(duì)象指向分配的內(nèi)存地址芋酌。加上 volatile 是為了讓以上的三步操作順序執(zhí)行增显,反之有可能第二步在第三步之前被執(zhí)行就有可能某個(gè)線程拿到的單例對(duì)象是還沒(méi)有初始化的,以致于報(bào)錯(cuò)隔嫡。
class Singleton {
    // 加volatile關(guān)鍵字是防止指令重排甸怕,保證了變量在內(nèi)存中的可見(jiàn)性,但不能保證原子性腮恩。
    private volatile static Singleton instance = null;
    private Singleton() { }
    public static Singleton getInstance() {
        if(instance==null) {// 此處是為了減少加鎖
            synchronized (Singleton.class) {
                if(instance==null) {// 此處為了并發(fā)進(jìn)來(lái)后不要重復(fù)new對(duì)象
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

volatile能取代synchronized嗎梢杭?

synchronized關(guān)鍵字是防止多個(gè)線程同時(shí)執(zhí)行一段代碼,那么就會(huì)很影響程序執(zhí)行效率秸滴,而volatile關(guān)鍵字在某些情況下性能要優(yōu)于synchronized武契,但是volatile關(guān)鍵字是無(wú)法替代synchronized關(guān)鍵字的,因?yàn)関olatile關(guān)鍵字無(wú)法保證操作的原子性荡含。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末咒唆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子释液,更是在濱河造成了極大的恐慌全释,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,946評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件误债,死亡現(xiàn)場(chǎng)離奇詭異浸船,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)寝蹈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門李命,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人箫老,你說(shuō)我怎么就攤上這事封字。” “怎么了耍鬓?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,716評(píng)論 0 364
  • 文/不壞的土叔 我叫張陵阔籽,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我牲蜀,道長(zhǎng)仿耽,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,222評(píng)論 1 300
  • 正文 為了忘掉前任各薇,我火速辦了婚禮项贺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘峭判。我一直安慰自己开缎,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,223評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布林螃。 她就那樣靜靜地躺著奕删,像睡著了一般。 火紅的嫁衣襯著肌膚如雪疗认。 梳的紋絲不亂的頭發(fā)上完残,一...
    開(kāi)封第一講書(shū)人閱讀 52,807評(píng)論 1 314
  • 那天伏钠,我揣著相機(jī)與錄音,去河邊找鬼谨设。 笑死熟掂,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的扎拣。 我是一名探鬼主播赴肚,決...
    沈念sama閱讀 41,235評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼二蓝!你這毒婦竟也來(lái)了誉券?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,189評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤刊愚,失蹤者是張志新(化名)和其女友劉穎踊跟,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體鸥诽,經(jīng)...
    沈念sama閱讀 46,712評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡琴锭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,775評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了衙传。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片决帖。...
    茶點(diǎn)故事閱讀 40,926評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蓖捶,靈堂內(nèi)的尸體忽然破棺而出地回,到底是詐尸還是另有隱情,我是刑警寧澤俊鱼,帶...
    沈念sama閱讀 36,580評(píng)論 5 351
  • 正文 年R本政府宣布刻像,位于F島的核電站,受9級(jí)特大地震影響并闲,放射性物質(zhì)發(fā)生泄漏细睡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,259評(píng)論 3 336
  • 文/蒙蒙 一帝火、第九天 我趴在偏房一處隱蔽的房頂上張望溜徙。 院中可真熱鬧,春花似錦犀填、人聲如沸蠢壹。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,750評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)图贸。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間疏日,已是汗流浹背偿洁。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,867評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,368評(píng)論 3 379
  • 正文 我出身青樓盼理,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子溉委,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,930評(píng)論 2 361