volatile關(guān)鍵字

在Java相關(guān)的崗位面試中,很多面試官都喜歡考察面試者對(duì)Java并發(fā)的了解程度吹散,而以volatile關(guān)鍵字作為一個(gè)小的切入點(diǎn),往往可以一問(wèn)到底霎槐,把Java內(nèi)存模型(JMM),Java并發(fā)編程的一些特性都牽扯出來(lái)梦谜,深入地話還可以考察JVM底層實(shí)現(xiàn)以及操作系統(tǒng)的相關(guān)知識(shí)丘跌。

下面我們以一次假想的面試過(guò)程,來(lái)深入了解下volitile關(guān)鍵字吧唁桩!

面試官: Java并發(fā)這塊了解的怎么樣闭树?說(shuō)說(shuō)你對(duì)volatile關(guān)鍵字的理解

就我理解的而言,被volatile修飾的共享變量荒澡,就具有了以下兩點(diǎn)特性:

1 . 保證了不同線程對(duì)該變量操作的內(nèi)存可見(jiàn)性;

2 . 禁止指令重排序

面試官: 能不能詳細(xì)說(shuō)下什么是內(nèi)存可見(jiàn)性报辱,什么又是重排序呢?

這個(gè)聊起來(lái)可就多了单山,我還是從Java內(nèi)存模型說(shuō)起吧碍现。

Java虛擬機(jī)規(guī)范試圖定義一種Java內(nèi)存模型(JMM),來(lái)屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問(wèn)差異,讓Java程序在各種平臺(tái)上都能達(dá)到一致的內(nèi)存訪問(wèn)效果米奸。簡(jiǎn)單來(lái)說(shuō)昼接,由于CPU執(zhí)行指令的速度是很快的,但是內(nèi)存訪問(wèn)的速度就慢了很多悴晰,相差的不是一個(gè)數(shù)量級(jí)慢睡,所以搞處理器的那群大佬們又在CPU里加了好幾層高速緩存。

在Java內(nèi)存模型里铡溪,對(duì)上述的優(yōu)化又進(jìn)行了一波抽象漂辐。JMM規(guī)定所有變量都是存在主存中的,類(lèi)似于上面提到的普通內(nèi)存棕硫,每個(gè)線程又包含自己的工作內(nèi)存髓涯,方便理解就可以看成CPU上的寄存器或者高速緩存。所以線程的操作都是以工作內(nèi)存為主哈扮,它們只能訪問(wèn)自己的工作內(nèi)存复凳,且工作前后都要把值在同步回主內(nèi)存瘤泪。

這么說(shuō)得我自己都有些不清楚了,拿張紙畫(huà)一下:

Java面試官最?lèi)?ài)的volatile關(guān)鍵字育八,你是愛(ài)還是不愛(ài)对途?

在線程執(zhí)行時(shí),首先會(huì)從主存中read變量值髓棋,再load到工作內(nèi)存中的副本中实檀,然后再傳給處理器執(zhí)行,執(zhí)行完畢后再給工作內(nèi)存中的副本賦值按声,隨后工作內(nèi)存再把值傳回給主存膳犹,主存中的值才更新。

使用工作內(nèi)存和主存签则,雖然加快的速度须床,但是也帶來(lái)了一些問(wèn)題。比如看下面一個(gè)例子:

<pre class="ql-align-justify" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">i = i + 1;
</pre>

假設(shè)i初值為0渐裂,當(dāng)只有一個(gè)線程執(zhí)行它時(shí)豺旬,結(jié)果肯定得到1,當(dāng)兩個(gè)線程執(zhí)行時(shí)柒凉,會(huì)得到結(jié)果2嗎族阅?這倒不一定了∠ダ蹋可能存在這種情況:

<pre class="ql-align-justify" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">線程1:load i from 主存 // i = 0
i + 1 // i = 1
線程2:load i from主存 // 因?yàn)榫€程1還沒(méi)將i的值寫(xiě)回主存坦刀,所以i還是0
i + 1 //i = 1
線程1: save i to 主存
線程2:save i to 主存
</pre>

如果兩個(gè)線程按照上面的執(zhí)行流程,那么i最后的值居然是1了蔬咬。如果最后的寫(xiě)回生效的慢鲤遥,你再讀取i的值,都可能是0林艘,這就是緩存不一致問(wèn)題渴频。

下面就要提到你剛才問(wèn)到的問(wèn)題了,JMM主要就是圍繞著如何在并發(fā)過(guò)程中如何處理原子性北启、可見(jiàn)性和有序性這3個(gè)特征來(lái)建立的卜朗,通過(guò)解決這三個(gè)問(wèn)題,可以解除緩存不一致的問(wèn)題咕村。而volatile跟可見(jiàn)性和有序性都有關(guān)场钉。

面試官:那你具體說(shuō)說(shuō)這三個(gè)特性呢?

1 . 原子性(Atomicity): Java中懈涛,對(duì)基本數(shù)據(jù)類(lèi)型的讀取和賦值操作是原子性操作逛万,所謂原子性操作就是指這些操作是不可中斷的,要做一定做完,要么就沒(méi)有執(zhí)行宇植。

比如:

<pre class="ql-align-justify" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">i = 2;
j = i;
i++;
i = i + 1得封;
</pre>

上面4個(gè)操作中,i=2是讀取操作指郁,必定是原子性操作忙上,j=i你以為是原子性操作,其實(shí)吧闲坎,分為兩步疫粥,一是讀取i的值,然后再賦值給j,這就是2步操作了腰懂,稱(chēng)不上原子操作梗逮,i++和i = i + 1其實(shí)是等效的,讀取i的值绣溜,加1慷彤,再寫(xiě)回主存,那就是3步操作了怖喻。所以上面的舉例中底哗,最后的值可能出現(xiàn)多種情況,就是因?yàn)闈M足不了原子性罢防。

這么說(shuō)來(lái)艘虎,只有簡(jiǎn)單的讀取唉侄,賦值是原子操作咒吐,還只能是用數(shù)字賦值,用變量的話還多了一步讀取變量值的操作属划。有個(gè)例外是恬叹,虛擬機(jī)規(guī)范中允許對(duì)64位數(shù)據(jù)類(lèi)型(long和double),分為2次32為的操作來(lái)處理同眯,但是最新JDK實(shí)現(xiàn)還是實(shí)現(xiàn)了原子操作的绽昼。

JMM只實(shí)現(xiàn)了基本的原子性,像上面i++那樣的操作须蜗,必須借助于synchronized和Lock來(lái)保證整塊代碼的原子性了硅确。線程在釋放鎖之前,必然會(huì)把i的值刷回到主存的明肮。

2 . 可見(jiàn)性(Visibility):

說(shuō)到可見(jiàn)性菱农,Java就是利用volatile來(lái)提供可見(jiàn)性的。

當(dāng)一個(gè)變量被volatile修飾時(shí)柿估,那么對(duì)它的修改會(huì)立刻刷新到主存循未,當(dāng)其它線程需要讀取該變量時(shí),會(huì)去內(nèi)存中讀取新值秫舌。而普通變量則不能保證這一點(diǎn)的妖。

其實(shí)通過(guò)synchronized和Lock也能夠保證可見(jiàn)性绣檬,線程在釋放鎖之前,會(huì)把共享變量值都刷回主存嫂粟,但是synchronized和Lock的開(kāi)銷(xiāo)都更大娇未。

3 . 有序性(Ordering)

JMM是允許編譯器和處理器對(duì)指令重排序的,但是規(guī)定了as-if-serial語(yǔ)義赋元,即不管怎么重排序忘蟹,程序的執(zhí)行結(jié)果不能改變。比如下面的程序段:

<pre class="ql-align-justify" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">double pi = 3.14; //A
double r = 1; //B
double s= pi * r * r;//C
</pre>

上面的語(yǔ)句搁凸,可以按照A->B->C執(zhí)行媚值,結(jié)果為3.14,但是也可以按照B->A->C的順序執(zhí)行,因?yàn)锳护糖、B是兩句獨(dú)立的語(yǔ)句褥芒,而C則依賴(lài)于A、B嫡良,所以A驹针、B可以重排序,但是C卻不能排到A傲醉、B的前面乌助。JMM保證了重排序不會(huì)影響到單線程的執(zhí)行,但是在多線程中卻容易出問(wèn)題很澄。

比如這樣的代碼:

<pre class="ql-align-justify" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">int a = 0;
bool flag = false;
public void write() {
a = 2; //1
flag = true; //2
}
public void multiply() {
if (flag) { //3
int ret = a * a;//4
}

}
</pre>

假如有兩個(gè)線程執(zhí)行上述代碼段京闰,線程1先執(zhí)行write,隨后線程2再執(zhí)行multiply甩苛,最后ret的值一定是4嗎蹂楣?結(jié)果不一定:

Java面試官最?lèi)?ài)的volatile關(guān)鍵字,你是愛(ài)還是不愛(ài)讯蒲?

如圖所示痊土,write方法里的1和2做了重排序,線程1先對(duì)flag賦值為true墨林,隨后執(zhí)行到線程2赁酝,ret直接計(jì)算出結(jié)果,再到線程1旭等,這時(shí)候a才賦值為2,很明顯遲了一步酌呆。

這時(shí)候可以為flag加上volatile關(guān)鍵字,禁止重排序辆雾,可以確保程序的“有序性”肪笋,也可以上重量級(jí)的synchronized和Lock來(lái)保證有序性,它們能保證那一塊區(qū)域里的代碼都是一次性執(zhí)行完畢的。

另外,JMM具備一些先天的有序性,即不需要通過(guò)任何手段就可以保證的有序性藤乙,通常稱(chēng)為happens-before原則猜揪。<<JSR-133:Java Memory Model and Thread Specification>>定義了如下happens-before規(guī)則:

  1. 程序順序規(guī)則:一個(gè)線程中的每個(gè)操作,happens-before于該線程中的任意后續(xù)操作
  2. 監(jiān)視器鎖規(guī)則:對(duì)一個(gè)線程的解鎖坛梁,happens-before于隨后對(duì)這個(gè)線程的加鎖
  3. volatile變量規(guī)則:對(duì)一個(gè)volatile域的寫(xiě)而姐,happens-before于后續(xù)對(duì)這個(gè)volatile域的讀
  4. 傳遞性:如果A happens-before B ,且 B happens-before C, 那么 A happens-before C
  5. start()規(guī)則:如果線程A執(zhí)行操作ThreadB_start()(啟動(dòng)線程B) , 那么A線程的ThreadB_start()happens-before 于B中的任意操作
  6. join()原則:如果A執(zhí)行ThreadB.join()并且成功返回,那么線程B中的任意操作happens-before于線程A從ThreadB.join()操作成功返回划咐。
  7. interrupt()原則:對(duì)線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程代碼檢測(cè)到中斷事件的發(fā)生拴念,可以通過(guò)Thread.interrupted()方法檢測(cè)是否有中斷發(fā)生
  8. finalize()原則:一個(gè)對(duì)象的初始化完成先行發(fā)生于它的finalize()方法的開(kāi)始

第1條規(guī)則程序順序規(guī)則是說(shuō)在一個(gè)線程里,所有的操作都是按順序的褐缠,但是在JMM里其實(shí)只要執(zhí)行結(jié)果一樣政鼠,是允許重排序的,這邊的happens-before強(qiáng)調(diào)的重點(diǎn)也是單線程執(zhí)行結(jié)果的正確性队魏,但是無(wú)法保證多線程也是如此公般。

第2條規(guī)則監(jiān)視器規(guī)則其實(shí)也好理解,就是在加鎖之前胡桨,確定這個(gè)鎖之前已經(jīng)被釋放了官帘,才能繼續(xù)加鎖。

第3條規(guī)則昧谊,就適用到所討論的volatile刽虹,如果一個(gè)線程先去寫(xiě)一個(gè)變量,另外一個(gè)線程再去讀呢诬,那么寫(xiě)入操作一定在讀操作之前涌哲。

第4條規(guī)則,就是happens-before的傳遞性馅巷。

后面幾條就不再一一贅述了膛虫。

面試官:volatile關(guān)鍵字如何滿足并發(fā)編程的三大特性的草姻?

那就要重提volatile變量規(guī)則:對(duì)一個(gè)volatile域的寫(xiě)钓猬,happens-before于后續(xù)對(duì)這個(gè)volatile域的讀。

這條再拎出來(lái)說(shuō)撩独,其實(shí)就是如果一個(gè)變量聲明成是volatile的敞曹,那么當(dāng)我讀變量時(shí),總是能讀到它的最新值综膀,這里最新值是指不管其它哪個(gè)線程對(duì)該變量做了寫(xiě)操作澳迫,都會(huì)立刻被更新到主存里,我也能從主存里讀到這個(gè)剛寫(xiě)入的值剧劝。也就是說(shuō)volatile關(guān)鍵字可以保證可見(jiàn)性以及有序性橄登。

繼續(xù)拿上面的一段代碼舉例:

<pre class="ql-align-justify" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">int a = 0;
bool flag = false;
public void write() {
a = 2; //1
flag = true; //2
}
public void multiply() {
if (flag) { //3
int ret = a * a;//4
}

}
</pre>

這段代碼不僅僅受到重排序的困擾,即使1、2沒(méi)有重排序拢锹。3也不會(huì)那么順利的執(zhí)行的谣妻。假設(shè)還是線程1先執(zhí)行write操作,線程2再執(zhí)行multiply操作卒稳,由于線程1是在工作內(nèi)存里把flag賦值為1蹋半,不一定立刻寫(xiě)回主存,所以線程2執(zhí)行時(shí)充坑,multiply再?gòu)闹鞔孀xflag值减江,仍然可能為false,那么括號(hào)里的語(yǔ)句將不會(huì)執(zhí)行捻爷。

如果改成下面這樣:

<pre class="ql-align-justify" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">int a = 0;
volatile bool flag = false;
public void write() {
a = 2; //1
flag = true; //2
}
public void multiply() {
if (flag) { //3
int ret = a * a;//4
}
}
</pre>

那么線程1先執(zhí)行write,線程2再執(zhí)行multiply辈灼。根據(jù)happens-before原則,這個(gè)過(guò)程會(huì)滿足以下3類(lèi)規(guī)則:

  1. 程序順序規(guī)則:1 happens-before 2; 3 happens-before 4; (volatile限制了指令重排序也榄,所以1 在2 之前執(zhí)行)
  2. volatile規(guī)則:2 happens-before 3
  3. 傳遞性規(guī)則:1 happens-before 4

從內(nèi)存語(yǔ)義上來(lái)看

當(dāng)寫(xiě)一個(gè)volatile變量時(shí)茵休,JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存中的共享變量刷新到主內(nèi)存

當(dāng)讀一個(gè)volatile變量時(shí),JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存置為無(wú)效手蝎,線程接下來(lái)將從主內(nèi)存中讀取共享變量榕莺。

面試官:volatile的兩點(diǎn)內(nèi)存語(yǔ)義能保證可見(jiàn)性和有序性,但是能保證原子性嗎棵介?

首先我回答是不能保證原子性钉鸯,要是說(shuō)能保證,也只是對(duì)單個(gè)volatile變量的讀/寫(xiě)具有原子性邮辽,但是對(duì)于類(lèi)似volatile++這樣的復(fù)合操作就無(wú)能為力了唠雕,比如下面的例子:

<pre class="ql-align-justify" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class Test {
public volatile int inc = 0;

public void increase() {
inc++;
}

public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}

while(Thread.activeCount()>1) //保證前面的線程都執(zhí)行完
Thread.yield();
System.out.println(test.inc);
}
</pre>

按道理來(lái)說(shuō)結(jié)果是10000,但是運(yùn)行下很可能是個(gè)小于10000的值吨述。有人可能會(huì)說(shuō)volatile不是保證了可見(jiàn)性啊岩睁,一個(gè)線程對(duì)inc的修改,另外一個(gè)線程應(yīng)該立刻看到按г啤捕儒!可是這里的操作inc++是個(gè)復(fù)合操作啊,包括讀取inc的值邓夕,對(duì)其自增刘莹,然后再寫(xiě)回主存。

假設(shè)線程A焚刚,讀取了inc的值為10点弯,這時(shí)候被阻塞了,因?yàn)闆](méi)有對(duì)變量進(jìn)行修改矿咕,觸發(fā)不了volatile規(guī)則抢肛。

線程B此時(shí)也讀讀inc的值狼钮,主存里inc的值依舊為10,做自增捡絮,然后立刻就被寫(xiě)回主存了燃领,為11。

此時(shí)又輪到線程A執(zhí)行锦援,由于工作內(nèi)存里保存的是10猛蔽,所以繼續(xù)做自增,再寫(xiě)回主存灵寺,11又被寫(xiě)了一遍曼库。所以雖然兩個(gè)線程執(zhí)行了兩次increase(),結(jié)果卻只加了一次略板。

有人說(shuō)毁枯,volatile不是會(huì)使緩存行無(wú)效的嗎?但是這里線程A讀取到線程B也進(jìn)行操作之前叮称,并沒(méi)有修改inc值种玛,所以線程B讀取的時(shí)候,還是讀的10瓤檐。

又有人說(shuō)赂韵,線程B將11寫(xiě)回主存,不會(huì)把線程A的緩存行設(shè)為無(wú)效嗎挠蛉?但是線程A的讀取操作已經(jīng)做過(guò)了啊祭示,只有在做讀取操作時(shí),發(fā)現(xiàn)自己緩存行無(wú)效谴古,才會(huì)去讀主存的值质涛,所以這里線程A只能繼續(xù)做自增了。

綜上所述掰担,在這種復(fù)合操作的情景下汇陆,原子性的功能是維持不了了。但是volatile在上面那種設(shè)置flag值的例子里带饱,由于對(duì)flag的讀/寫(xiě)操作都是單步的毡代,所以還是能保證原子性的。

要想保證原子性纠炮,只能借助于synchronized,Lock以及并發(fā)包下的atomic的原子操作類(lèi)了月趟,即對(duì)基本數(shù)據(jù)類(lèi)型的 自增(加1操作)灯蝴,自減(減1操作)恢口、以及加法操作(加一個(gè)數(shù)),減法操作(減一個(gè)數(shù))進(jìn)行了封裝穷躁,保證這些操作是原子性操作耕肩。

面試官:說(shuō)的還可以因妇,那你知道volatile底層的實(shí)現(xiàn)機(jī)制?

如果把加入volatile關(guān)鍵字的代碼和未加入volatile關(guān)鍵字的代碼都生成匯編代碼猿诸,會(huì)發(fā)現(xiàn)加入volatile關(guān)鍵字的代碼會(huì)多出一個(gè)lock前綴指令婚被。

lock前綴指令實(shí)際相當(dāng)于一個(gè)內(nèi)存屏障,內(nèi)存屏障提供了以下功能:

1 . 重排序時(shí)不能把后面的指令重排序到內(nèi)存屏障之前的位置

2 . 使得本CPU的Cache寫(xiě)入內(nèi)存

3 . 寫(xiě)入動(dòng)作也會(huì)引起別的CPU或者別的內(nèi)核無(wú)效化其Cache梳虽,相當(dāng)于讓新寫(xiě)入的值對(duì)別的線程可見(jiàn)址芯。

面試官:你在哪里會(huì)使用到volatile,舉兩個(gè)例子呢窜觉?

1. 狀態(tài)量標(biāo)記谷炸,就如上面對(duì)flag的標(biāo)記,我重新提一下:

<pre class="ql-align-justify" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">int a = 0;
volatile bool flag = false;
public void write() {
a = 2; //1
flag = true; //2
}
public void multiply() {
if (flag) { //3
int ret = a * a;//4
}
}
</pre>

這種對(duì)變量的讀寫(xiě)操作禀挫,標(biāo)記為volatile可以保證修改對(duì)線程立刻可見(jiàn)旬陡。比synchronized,Lock有一定的效率提升。

2.單例模式的實(shí)現(xiàn)语婴,典型的雙重檢查鎖定(DCL)

<pre class="ql-align-justify" style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">class Singleton{
private volatile static Singleton instance = null;

private Singleton() {

}

public static Singleton getInstance() {
if(instance==null) {
synchronized (Singleton.class) {
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}
</pre>

這是一種懶漢的單例模式描孟,使用時(shí)才創(chuàng)建對(duì)象,而且為了避免初始化操作的指令重排序砰左,給instance加上了volatile匿醒。

面試官:來(lái)給我們說(shuō)說(shuō)幾種單例模式的寫(xiě)法吧,還有上面這種用法缠导,你再詳細(xì)說(shuō)說(shuō)呢青抛?

好吧,這又是一個(gè)話題了酬核,volatile的問(wèn)題終于問(wèn)完了蜜另。。嫡意【俟澹看看你掌握了沒(méi)~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蔬螟,隨后出現(xiàn)的幾起案子此迅,更是在濱河造成了極大的恐慌,老刑警劉巖旧巾,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件耸序,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡鲁猩,警方通過(guò)查閱死者的電腦和手機(jī)坎怪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)廓握,“玉大人搅窿,你說(shuō)我怎么就攤上這事嘁酿。” “怎么了男应?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵闹司,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我沐飘,道長(zhǎng)游桩,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任耐朴,我火速辦了婚禮众弓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘隔箍。我一直安慰自己谓娃,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布蜒滩。 她就那樣靜靜地躺著滨达,像睡著了一般。 火紅的嫁衣襯著肌膚如雪俯艰。 梳的紋絲不亂的頭發(fā)上捡遍,一...
    開(kāi)封第一講書(shū)人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音竹握,去河邊找鬼画株。 笑死,一個(gè)胖子當(dāng)著我的面吹牛啦辐,可吹牛的內(nèi)容都是我干的谓传。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼芹关,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼续挟!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起侥衬,我...
    開(kāi)封第一講書(shū)人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤诗祸,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后轴总,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體直颅,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年怀樟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了功偿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡漂佩,死狀恐怖脖含,靈堂內(nèi)的尸體忽然破棺而出罪塔,到底是詐尸還是另有隱情投蝉,我是刑警寧澤养葵,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站瘩缆,受9級(jí)特大地震影響关拒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜庸娱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一着绊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧熟尉,春花似錦归露、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至往果,卻和暖如春疆液,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背陕贮。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工堕油, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人肮之。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓掉缺,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親戈擒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子攀圈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

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