?反制面試官 | 14張?jiān)韴D | 再也不怕被問 volatile!
絮叨
這一篇也算是Java并發(fā)編程的開篇,看了很多資料,但是輪到自己去整理去總結(jié)的時(shí)候,發(fā)現(xiàn)還是要多看幾遍資料才能完全理解。還有一個(gè)很重要的點(diǎn)就是,畫圖是加深印象和檢驗(yàn)自己是否理解的一個(gè)非常好的方法。
一、Volatile怎么念?

看到這個(gè)單詞一直不知道怎么發(fā)音
英?[?v?l?ta?l]??美?[?vɑ?l?tl]
adj. [化學(xué)]?揮發(fā)性的;不穩(wěn)定的;爆炸性的;反復(fù)無(wú)常的
那Java中volatile又是干啥的呢?
二、Java中volatile用來(lái)干啥?
Volatile是Java虛擬機(jī)提供的 輕量級(jí)的同步機(jī)制(三大特性)保證可見性 不保證原子性 禁止指令重排
要理解三大特性,就必須知道Java內(nèi)存模型(JMM),那JMM又是什么呢?

三、JMM又是啥?
這是一份精心總結(jié)的Java內(nèi)存模型思維導(dǎo)圖,拿去不謝。


3.1 為什么需要Java內(nèi)存模型?
??
Why:屏蔽各種硬件和操作系統(tǒng)的內(nèi)存訪問差異
JMM是Java內(nèi)存模型,也就是Java Memory Model,簡(jiǎn)稱JMM,本身是一種抽象的概念,實(shí)際上并不存在,它描述的是一組規(guī)則或規(guī)范,通過這組規(guī)范定義了程序中各個(gè)變量(包括實(shí)例字段,靜態(tài)字段和構(gòu)成數(shù)組對(duì)象的元素)的訪問方式。
3.2 到底什么是Java內(nèi)存模型?
1.定義程序中各種變量的訪問規(guī)則 2.把變量值存儲(chǔ)到內(nèi)存的底層細(xì)節(jié) 3.從內(nèi)存中取出變量值的底層細(xì)節(jié)
3.3 Java內(nèi)存模型的兩大內(nèi)存是啥?

主內(nèi)存 Java堆中對(duì)象實(shí)例數(shù)據(jù)部分 對(duì)應(yīng)于物理硬件的內(nèi)存 工作內(nèi)存 Java棧中的部分區(qū)域 優(yōu)先存儲(chǔ)于寄存器和高速緩存
3.4 Java內(nèi)存模型是怎么做的?
Java內(nèi)存模型的幾個(gè)規(guī)范:
1.所有變量存儲(chǔ)在主內(nèi)存
2.主內(nèi)存是虛擬機(jī)內(nèi)存的一部分
3.每條線程有自己的工作內(nèi)存
4.線程的工作內(nèi)存保存變量的主內(nèi)存副本
5.線程對(duì)變量的操作必須在工作內(nèi)存中進(jìn)行
6.不同線程之間無(wú)法直接訪問對(duì)方工作內(nèi)存中的變量
7.線程間變量值的傳遞均需要通過主內(nèi)存來(lái)完成
由于JVM運(yùn)行程序的實(shí)體是線程,而每個(gè)線程創(chuàng)建時(shí)JVM都會(huì)為其創(chuàng)建一個(gè)工作內(nèi)存(有些地方稱為??臻g),工作內(nèi)存是每個(gè)線程的私有數(shù)據(jù)區(qū)域,而Java內(nèi)存模型中規(guī)定所有變量都存儲(chǔ)在主內(nèi)存,主內(nèi)存是共享內(nèi)存區(qū)域,所有線程都可以訪問,但線程對(duì)變量的操作(讀取賦值等)必須在工作內(nèi)存中進(jìn)行,首先要將變量從主內(nèi)存拷貝到自己的工作內(nèi)存空間,然后對(duì)變量進(jìn)行操作,操作完成后再將變量寫會(huì)主內(nèi)存,不能直接操作主內(nèi)存中的變量,各個(gè)線程中的工作內(nèi)存中存儲(chǔ)著主內(nèi)存中的變量副本拷貝,因此不同的線程間無(wú)法訪問對(duì)方的工作內(nèi)存,線程間的通信(傳值)必須通過主內(nèi)存來(lái)完成,其簡(jiǎn)要訪問過程:

3.5 Java內(nèi)存模型的三大特性
可見性(當(dāng)一個(gè)線程修改了共享變量的值時(shí),其他線程能夠立即得知這個(gè)修改) 原子性(一個(gè)操作或一系列操作是不可分割的,要么同時(shí)成功,要么同時(shí)失?。?/section> 有序性(變量賦值操作的順序與程序代碼中的執(zhí)行順序一致)
關(guān)于有序性:如果在本線程內(nèi)觀察,所有的操作都是有序的;如果在一個(gè)線程中觀察另一個(gè)線程,所有的操作都是無(wú)序的。前半句是指“線程內(nèi)似表現(xiàn)為串行的語(yǔ)義”(Within-Thread As-If-Serial Semantics),后半句是指“指令重排序”現(xiàn)象和“工作內(nèi)存與主內(nèi)存同步延遲”現(xiàn)象。
四、能給個(gè)示例說下怎么用volatile的嗎?
考慮一下這種場(chǎng)景:
?有一個(gè)對(duì)象的字段
number初始化值=0,另外這個(gè)對(duì)象有一個(gè)公共方法setNumberTo100()可以設(shè)置number = 100,當(dāng)主線程通過子線程來(lái)調(diào)用setNumberTo100()后,主線程是否知道number值變了呢?答案:如果沒有使用volatile來(lái)定義number變量,則主線程不知道子線程更新了number的值。
?
(1)定義如上述所說的對(duì)象:ShareData
class?ShareData?{
????int?number?=?0;
????public?void?setNumberTo100()?{
????????this.number?=?100;
????}
}
(2)主線程中初始化一個(gè)子線程,名字叫做子線程
子線程先休眠3s,然后設(shè)置number=100。主線程不斷檢測(cè)的number值是否等于0,如果不等于0,則退出主線程。
public?class?volatileVisibility?{
????public?static?void?main(String[]?args)?{
????????//?資源類
????????ShareData?shareData?=?new?ShareData();
????????//?子線程?實(shí)現(xiàn)了Runnable接口的,lambda表達(dá)式
????????new?Thread(()?->?{
????????????System.out.println(Thread.currentThread().getName()?+?"\t?come?in");
????????????//?線程睡眠3秒,假設(shè)在進(jìn)行運(yùn)算
????????????try?{
????????????????TimeUnit.SECONDS.sleep(3);
????????????}?catch?(InterruptedException?e)?{
????????????????e.printStackTrace();
????????????}
????????????//?修改number的值
????????????myData.setNumberTo100();
????????????//?輸出修改后的值
????????????System.out.println(Thread.currentThread().getName()?+?"\t?update?number?value:"?+?myData.number);
????????},?"子線程").start();
????????while(myData.number?==?0)?{
????????????//?main線程就一直在這里等待循環(huán),直到number的值不等于零
????????}
????????//?按道理這個(gè)值是不可能打印出來(lái)的,因?yàn)橹骶€程運(yùn)行的時(shí)候,number的值為0,所以一直在循環(huán)
????????//?如果能輸出這句話,說明子線程在睡眠3秒后,更新的number的值,重新寫入到主內(nèi)存,并被main線程感知到了
????????System.out.println(Thread.currentThread().getName()?+?"\t?主線程感知到了?number?不等于?0");
????????/**
?????????*?最后輸出結(jié)果:
?????????*?子線程?????come?in
?????????*?子線程?????update?number?value:100
?????????*?最后線程沒有停止,并行沒有輸出"主線程知道了?number?不等于0"這句話,說明沒有用volatile修飾的變量,變量的更新是不可見的
?????????*/
????}
}

(3)我們用volatile修飾變量number
class?ShareData?{
????//volatile?修飾的關(guān)鍵字,是為了增加多個(gè)線程之間的可見性,只要有一個(gè)線程修改了內(nèi)存中的值,其它線程也能馬上感知
????volatile?int?number?=?0;
????public?void?setNumberTo100()?{
????????this.number?=?100;
????}
}
輸出結(jié)果:
子線程??come?in
子線程??update?number?value:100
main??主線程知道了?number?不等于?0
Process?finished?with?exit?code?0

「小結(jié):說明用volatile修飾的變量,當(dāng)某線程更新變量后,其他線程也能感知到。」
五、那為什么其他線程能感知到變量更新?

其實(shí)這里就是用到了“窺探(snooping)”協(xié)議。在說“窺探(snooping)”協(xié)議之前,首先談?wù)劸彺嬉恢滦缘膯栴}。
5.1 緩存一致性
當(dāng)多個(gè)CPU持有的緩存都來(lái)自同一個(gè)主內(nèi)存的拷貝,當(dāng)有其他CPU偷偷改了這個(gè)主內(nèi)存數(shù)據(jù)后,其他CPU并不知道,那拷貝的內(nèi)存將會(huì)和主內(nèi)存不一致,這就是緩存不一致。那我們?nèi)绾蝸?lái)保證緩存一致呢?這里就需要操作系統(tǒng)來(lái)共同制定一個(gè)同步規(guī)則來(lái)保證,而這個(gè)規(guī)則就有MESI協(xié)議。
如下圖所示,CPU2 偷偷將num修改為2,內(nèi)存中num也被修改為2,但是CPU1和CPU3并不知道num值變了。

5.2 MESI
當(dāng)CPU寫數(shù)據(jù)時(shí),如果發(fā)現(xiàn)操作的變量是共享變量,即在其它CPU中也存在該變量的副本,系統(tǒng)會(huì)發(fā)出信號(hào)通知其它CPU將該內(nèi)存變量的緩存行設(shè)置為無(wú)效。如下圖所示,CPU1和CPU3 中num=1已經(jīng)失效了。

當(dāng)其它CPU讀取這個(gè)變量的時(shí),發(fā)現(xiàn)自己緩存該變量的緩存行是無(wú)效的,那么它就會(huì)從內(nèi)存中重新讀取。
如下圖所示,CPU1和CPU3發(fā)現(xiàn)緩存的num值失效了,就重新從內(nèi)存讀取,num值更新為2。

5.3 總線嗅探
那其他CPU是怎么知道要將緩存更新為失效的呢?這里是用到了總線嗅探技術(shù)。
每個(gè)CPU不斷嗅探總線上傳播的數(shù)據(jù)來(lái)檢查自己緩存值是否過期了,如果處理器發(fā)現(xiàn)自己的緩存行對(duì)應(yīng)的內(nèi)存地址被修改,就會(huì)將當(dāng)前處理器的緩存行設(shè)置為無(wú)效狀態(tài),當(dāng)處理器對(duì)這個(gè)數(shù)據(jù)進(jìn)行修改操作的時(shí)候,會(huì)重新從內(nèi)存中把數(shù)據(jù)讀取到處理器緩存中。

5.4 總線風(fēng)暴
總線嗅探技術(shù)有哪些缺點(diǎn)?
由于MESI緩存一致性協(xié)議,需要不斷對(duì)主線進(jìn)行內(nèi)存嗅探,大量的交互會(huì)導(dǎo)致總線帶寬達(dá)到峰值。因此不要濫用volatile,可以用鎖來(lái)替代,看場(chǎng)景啦~
六、能演示下volatile為什么不保證原子性嗎?
原子性:一個(gè)操作或一系列操作是不可分割的,要么同時(shí)成功,要么同時(shí)失敗。
「這個(gè)定義和volatile啥關(guān)系呀,完全不能理解呀?Show me the code!」
考慮一下這種場(chǎng)景:
?當(dāng)20個(gè)線程同時(shí)給number自增1,執(zhí)行1000次以后,number的值為多少呢?
?
在單線程的場(chǎng)景,答案是20000,如果是多線程的場(chǎng)景下呢?答案是可能是20000,但很多情況下都是小于20000。
示例代碼:
package?com.jackson0714.passjava.threads;
/**
?演示volatile?不保證原子性
?*?@create:?2020-08-13?09:53
?*/
public?class?VolatileAtomicity?{
????public?static?volatile?int?number?=?0;
????public?static?void?increase()?{
????????number++;
????}
????public?static?void?main(String[]?args)?{
????????for?(int?i?=?0;?i?50;?i++)?{
????????????new?Thread(()?->?{
????????????????for?(int?j?=?0;?j?1000;?j++)?{
????????????????????increase();
????????????????}
????????????},?String.valueOf(i)).start();
????????}
????????//?當(dāng)所有累加線程都結(jié)束
????????while(Thread.activeCount()?>?2)?{
????????????Thread.yield();
????????}
????????System.out.println(number);
????}
}
執(zhí)行結(jié)果:第一次19144,第二次20000,第三次19378。



我們來(lái)分析一下increase()方法,通過反編譯工具javap得到如下匯編代碼:
??public?static?void?increase();
????Code:
???????0:?getstatic?????#2??????????????????//?Field?number:I
???????3:?iconst_1
???????4:?iadd
???????5:?putstatic?????#2??????????????????//?Field?number:I
???????8:?return
number++其實(shí)執(zhí)行了3條指令:
?getstatic:拿number的原始值 iadd:進(jìn)行加1操作 putfield:把加1后的值寫回
?
執(zhí)行了getstatic指令number的值取到操作棧頂時(shí),volatile關(guān)鍵字保證了number的值在此時(shí)是正確的,但是在執(zhí)行iconst_1、iadd這些指令的時(shí)候,其他線程可能已經(jīng)把number的值改變了,而操作棧頂?shù)闹稻妥兂闪诉^期的數(shù)據(jù),所以putstatic指令執(zhí)行后就可能把較小的number值同步回主內(nèi)存之中。
總結(jié)如下:
?在執(zhí)行number++這行代碼時(shí),即使使用volatile修飾number變量,在執(zhí)行期間,還是有可能被其他線程修改,沒有保證原子性。
?
七、怎么保證輸出結(jié)果是20000呢?
7.1 synchronized同步代碼塊
我們可以通過使用synchronized同步代碼塊來(lái)保證原子性。從而使結(jié)果等于20000
public?synchronized?static?void?increase()?{
???number++;
}

但是使用synchronized太重了,會(huì)造成阻塞,只有一個(gè)線程能進(jìn)入到這個(gè)方法。我們可以使用Java并發(fā)包(JUC)中的AtomicInterger工具包。
7.2 AtomicInterger原子性操作
我們來(lái)看看AtomicInterger原子自增的方法getAndIncrement()

public?static?AtomicInteger?atomicInteger?=?new?AtomicInteger();
public?static?void?main(String[]?args)?{
????for?(int?i?=?0;?i?20;?i++)?{
????????new?Thread(()?->?{
????????????for?(int?j?=?0;?j?1000;?j++)?{
????????????????atomicInteger.getAndIncrement();
????????????}
????????},?String.valueOf(i)).start();
????}
????//?當(dāng)所有累加線程都結(jié)束
????while(Thread.activeCount()?>?2)?{
????????Thread.yield();
????}
????System.out.println(atomicInteger);
}
多次運(yùn)行的結(jié)果都是20000。

八、禁止指令重排又是啥?
說到指令重排就得知道為什么要重排,有哪幾種重排。
如下圖所示,指令執(zhí)行順序是按照1>2>3>4的順序,經(jīng)過重排后,執(zhí)行順序更新為指令3->4->2->1。

會(huì)不會(huì)感覺到重排把指令順序都打亂了,這樣好嗎?
可以回想下小學(xué)時(shí)候的數(shù)學(xué)題:2+3-5=?,如果把運(yùn)算順序改為3-5+2=?,結(jié)果也是一樣的。所以指令重排是要保證單線程下程序結(jié)果不變的情況下做重排。
8.1 為什么要重排
計(jì)算機(jī)在執(zhí)行程序時(shí),為了提高性能,編譯器和處理器常常會(huì)對(duì)指令做重排序。
8.2 有哪幾種重排
1.編譯器優(yōu)化重排:編譯器在不改變單線程程序語(yǔ)義的前提下,可以重新安排語(yǔ)句的執(zhí)行順序。
2.指令級(jí)的并行重排:現(xiàn)代處理器采用了指令級(jí)并行技術(shù)來(lái)將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性,處理器可以改變語(yǔ)句對(duì)應(yīng)機(jī)器指令的執(zhí)行順序。
3.內(nèi)存系統(tǒng)的重排:由于處理器使用緩存和讀/寫緩沖區(qū),這使得加載和存儲(chǔ)操作看上去可能是在亂序執(zhí)行。

注意:
單線程環(huán)境里面確保最終執(zhí)行結(jié)果和代碼順序的結(jié)果一致
處理器在進(jìn)行重排序時(shí),必須要考慮指令之間的
數(shù)據(jù)依賴性多線程環(huán)境中線程交替執(zhí)行,由于編譯器優(yōu)化重排的存在,兩個(gè)線程中使用的變量能否保證一致性是無(wú)法確定的,結(jié)果無(wú)法預(yù)測(cè)。
8.3 舉個(gè)例子來(lái)說說多線程中的指令重排?
設(shè)想一下這種場(chǎng)景:定義了變量num=0和變量flag=false,線程1調(diào)用初始化函數(shù)init()執(zhí)行后,線程調(diào)用add()方法,當(dāng)另外線程判斷flag=true后,執(zhí)行num+100操作,那么我們預(yù)期的結(jié)果是num會(huì)等于101,但因?yàn)橛兄噶钪嘏诺目赡?,num=1和flag=true執(zhí)行順序可能會(huì)顛倒,以至于num可能等于100
public?class?VolatileResort?{
????static?int?num?=?0;
????static?boolean?flag?=?false;
????public?static?void?init()?{
????????num=?1;
????????flag?=?true;
????}
????public?static?void?add()?{
????????if?(flag)?{
????????????num?=?num?+?5;
????????????System.out.println("num:"?+?num);
????????}
????}
????public?static?void?main(String[]?args)?{
????????init();
????????new?Thread(()?->?{
????????????add();
????????},"子線程").start();
????}
}
先看線程1中指令重排:
num= 1;flag = true; 的執(zhí)行順序變?yōu)?code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">flag=true;num = 1;,如下圖所示的時(shí)序圖

如果線程2 num=num+5 在線程1設(shè)置num=1之前執(zhí)行,那么線程2的num變量值為5。如下圖所示的時(shí)序圖。

8.4 volatile怎么實(shí)現(xiàn)禁止指令重排?
我們使用volatile定義flag變量:
static?volatile?boolean?flag?=?false;
「如何實(shí)現(xiàn)禁止指令重排:」
原理:在volatile生成的指令序列前后插入內(nèi)存屏障(Memory Barries)來(lái)禁止處理器重排序。
「有如下四種內(nèi)存屏障:」

「volatile寫的場(chǎng)景如何插入內(nèi)存屏障:」
在每個(gè)volatile寫操作的前面插入一個(gè)StoreStore屏障(寫-寫 屏障)。
在每個(gè)volatile寫操作的后面插入一個(gè)StoreLoad屏障(寫-讀 屏障)。

?StoreStore屏障可以保證在volatile寫(flag賦值操作flag=true)之前,其前面的所有普通寫(num的賦值操作num=1) 操作已經(jīng)對(duì)任意處理器可見了,保障所有普通寫在volatile寫之前刷新到主內(nèi)存。
?
「volatile讀場(chǎng)景如何插入內(nèi)存屏障:」
在每個(gè)volatile讀操作的后面插入一個(gè)LoadLoad屏障(讀-讀 屏障)。
在每個(gè)volatile讀操作的后面插入一個(gè)LoadStore屏障(讀-寫 屏障)。

?LoadStore屏障可以保證其后面的所有普通寫(num的賦值操作num=num+5) 操作必須在volatile讀(if(flag))之后執(zhí)行。
?
十、volatile常見應(yīng)用
這里舉一個(gè)應(yīng)用,雙重檢測(cè)鎖定的單例模式
package?com.jackson0714.passjava.threads;
/**
?演示volatile?單例模式應(yīng)用(雙邊檢測(cè))
?*?@author:?悟空聊架構(gòu)
?*?@create:?2020-08-17
?*/
class?VolatileSingleton?{
????private?static?VolatileSingleton?instance?=?null;
????private?VolatileSingleton()?{
????????System.out.println(Thread.currentThread().getName()?+?"\t?我是構(gòu)造方法SingletonDemo");
????}
????public?static?VolatileSingleton?getInstance()?{
????????//?第一重檢測(cè)
????????if(instance?==?null)?{
????????????//?鎖定代碼塊
????????????synchronized?(VolatileSingleton.class)?{
????????????????//?第二重檢測(cè)
????????????????if(instance?==?null)?{
????????????????????//?實(shí)例化對(duì)象
????????????????????instance?=?new?VolatileSingleton();
????????????????}
????????????}
????????}
????????return?instance;
????}
}
代碼看起來(lái)沒有問題,但是 instance = new VolatileSingleton();其實(shí)可以看作三條偽代碼:
memory?=?allocate();?//?1、分配對(duì)象內(nèi)存空間
instance(memory);?//?2、初始化對(duì)象
instance?=?memory;?//?3、設(shè)置instance指向剛剛分配的內(nèi)存地址,此時(shí)instance?!=?null
步驟2 和 步驟3之間不存在 數(shù)據(jù)依賴關(guān)系,而且無(wú)論重排前 還是重排后,程序的執(zhí)行結(jié)果在單線程中并沒有改變,因此這種重排優(yōu)化是允許的。
memory?=?allocate();?//?1、分配對(duì)象內(nèi)存空間
instance?=?memory;?//?3、設(shè)置instance指向剛剛分配的內(nèi)存地址,此時(shí)instance?!=?null,但是對(duì)象還沒有初始化完成
instance(memory);?//?2、初始化對(duì)象
如果另外一個(gè)線程執(zhí)行:if(instance == null)時(shí),則返回剛剛分配的內(nèi)存地址,但是對(duì)象還沒有初始化完成,拿到的instance是個(gè)假的。如下圖所示:

解決方案:定義instance為volatile變量
private?static?volatile?VolatileSingleton?instance?=?null;
十一、volatile都不保證原子性,為啥我們還要用它?
奇怪的是,volatile都不保證原子性,為啥我們還要用它?
volatile是輕量級(jí)的同步機(jī)制,對(duì)性能的影響比synchronized小。
?典型的用法:檢查某個(gè)狀態(tài)標(biāo)記以判斷是否退出循環(huán)。
?
比如線程試圖通過類似于數(shù)綿羊的傳統(tǒng)方法進(jìn)入休眠狀態(tài),為了使這個(gè)示例能正確執(zhí)行,asleep必須為volatile變量。否則,當(dāng)asleep被另一個(gè)線程修改時(shí),執(zhí)行判斷的線程卻發(fā)現(xiàn)不了。
「那為什么我們不直接用synchorized,lock鎖?它們既可以保證可見性,又可以保證原子性為何不用呢?」
因?yàn)閟ynchorized和lock是排他鎖(悲觀鎖),如果有多個(gè)線程需要訪問這個(gè)變量,將會(huì)發(fā)生競(jìng)爭(zhēng),只有一個(gè)線程可以訪問這個(gè)變量,其他線程被阻塞了,會(huì)影響程序的性能。
?注意:當(dāng)且僅當(dāng)滿足以下所有條件時(shí),才應(yīng)該用volatile變量
?
對(duì)變量的寫入操作不依賴變量的當(dāng)前值,或者你能確保只有單個(gè)線程更新變量的值。 該變量不會(huì)與其他的狀態(tài)一起納入不變性條件中。 在訪問變量時(shí)不需要加鎖。
十二、volatile和synchronzied的區(qū)別
volatile只能修飾實(shí)例變量和類變量,synchronized可以修飾方法和代碼塊。 volatile不保證原子性,而synchronized保證原子性 volatile 不會(huì)造成阻塞,而synchronized可能會(huì)造成阻塞 volatile 輕量級(jí)鎖,synchronized重量級(jí)鎖 volatile 和synchronized都保證了可見性和有序性
十三、小結(jié)
volatile 保證了可見性:當(dāng)一個(gè)線程修改了共享變量的值時(shí),其他線程能夠立即得知這個(gè)修改。 volatile 保證了單線程下指令不重排:通過插入內(nèi)存屏障保證指令執(zhí)行順序。 volatitle不保證原子性,如a++這種自增操作是有并發(fā)風(fēng)險(xiǎn)的,比如扣減庫(kù)存、發(fā)放優(yōu)惠券的場(chǎng)景。 volatile 類型的64位的long型和double型變量,對(duì)該變量的讀/寫具有原子性。 volatile 可以用在雙重檢鎖的單例模式中,比synchronized性能更好。 volatile 可以用在檢查某個(gè)狀態(tài)標(biāo)記以判斷是否退出循環(huán)。
參考資料:
