在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à)一下:
在線程執(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é)果不一定:
如圖所示痊土,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ī)則:
- 程序順序規(guī)則:一個(gè)線程中的每個(gè)操作,happens-before于該線程中的任意后續(xù)操作
- 監(jiān)視器鎖規(guī)則:對(duì)一個(gè)線程的解鎖坛梁,happens-before于隨后對(duì)這個(gè)線程的加鎖
- volatile變量規(guī)則:對(duì)一個(gè)volatile域的寫(xiě)而姐,happens-before于后續(xù)對(duì)這個(gè)volatile域的讀
- 傳遞性:如果A happens-before B ,且 B happens-before C, 那么 A happens-before C
- start()規(guī)則:如果線程A執(zhí)行操作ThreadB_start()(啟動(dòng)線程B) , 那么A線程的ThreadB_start()happens-before 于B中的任意操作
- join()原則:如果A執(zhí)行ThreadB.join()并且成功返回,那么線程B中的任意操作happens-before于線程A從ThreadB.join()操作成功返回划咐。
- interrupt()原則:對(duì)線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程代碼檢測(cè)到中斷事件的發(fā)生拴念,可以通過(guò)Thread.interrupted()方法檢測(cè)是否有中斷發(fā)生
- 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ī)則:
- 程序順序規(guī)則:1 happens-before 2; 3 happens-before 4; (volatile限制了指令重排序也榄,所以1 在2 之前執(zhí)行)
- volatile規(guī)則:2 happens-before 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)~