volatile 美[?vɑ?l?tl]
adj. 易變的; 無(wú)定性的; 無(wú)常性的; 可能急劇波動(dòng)的; 不穩(wěn)定的; 易惡化的; 易揮發(fā)的; 易發(fā)散的;
語(yǔ)義
保證任何一個(gè)線程改變了volatile修飾的變量,這個(gè)改動(dòng)對(duì)其他線程都是可見(jiàn)的
禁止該條指令重排序
線程間可見(jiàn)
看下下面一段代碼:
//線程1
boolean stop = false;
while(!stop){
doSomething();
}
//線程2
stop = true;
這種情況下,線程2對(duì)stop的修改可能不會(huì)影響線程1的運(yùn)行,也可能會(huì)影響線程1的運(yùn)行;
假設(shè)線程1和線程2是在不同的處理器中運(yùn)行,線程2修改了stop的值,按照現(xiàn)代處理器的設(shè)計(jì)思路,只會(huì)改動(dòng)cpu緩存cache中的值,過(guò)了一段時(shí)間后才會(huì)同步到主存中;對(duì)于線程2同樣讀取的stop值也是讀的其cache中的值,所以兩個(gè)線程的stop何時(shí)同步就變得不確定。volatile就是為了解決此類(lèi)問(wèn)題而設(shè)計(jì),后面會(huì)詳細(xì)說(shuō)明怎么解決的。
什么是指令重排序
為了盡可能減少內(nèi)存操作速度遠(yuǎn)慢于CPU運(yùn)行速度所帶來(lái)的CPU空置的影響,虛擬機(jī)會(huì)按照自己的一些規(guī)則(這規(guī)則后面再敘述)將程序編寫(xiě)順序打亂——即寫(xiě)在后面的代碼在時(shí)間順序上可能會(huì)先執(zhí)行,而寫(xiě)在前面的代碼會(huì)后執(zhí)行——以盡可能充分地利用CPU。比方說(shuō)new一個(gè)byte[1024*1024]的數(shù)組,其后的操作可能不等其地址分配完畢前就執(zhí)行。
但是,不管怎么重排序,單線程的執(zhí)行結(jié)果肯定是和程序順序一致的
public void execute(){
int a=0;
int b=1;
int c=a+b;
}
不管a=0還是b=1是什么順序,c=a+b肯定在這兩個(gè)語(yǔ)句之后。虛擬機(jī)有一套重排序的規(guī)則,保證這個(gè)結(jié)果。
重排序,總結(jié)下來(lái)就是,單個(gè)線程里看所有操作都是有序的,但是看別的線程,操作總是亂七八糟的。
volatile聲明的變量會(huì)確保本變量前的語(yǔ)句均被執(zhí)行
//線程1:
context = loadContext(); //語(yǔ)句1
//如果inited不被volatile修飾,可能因?yàn)檎Z(yǔ)句2優(yōu)先執(zhí)行,導(dǎo)致線程2報(bào)錯(cuò),加了volatile修飾符后,語(yǔ)句2處理前,語(yǔ)句1肯定已經(jīng)執(zhí)行完畢,不會(huì)出現(xiàn)這個(gè)問(wèn)題。
volatile inited = true; //語(yǔ)句2
//線程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
實(shí)現(xiàn)原理
volatile修飾的變量編譯后,會(huì)在變量前加個(gè)lock前綴指令,這個(gè)指令相當(dāng)于一個(gè)內(nèi)存屏障,主要提供三個(gè)功能:
- 它會(huì)強(qiáng)制對(duì)緩存的修改立即寫(xiě)入主存中
- 如果是寫(xiě)操作,其他cpu會(huì)受到總線信號(hào),緩存中對(duì)應(yīng)的數(shù)據(jù)失效,后續(xù)再讀時(shí),會(huì)重新從主存中讀取。
- 確保重排指令時(shí),其后的指令不會(huì)被重排到內(nèi)存屏障前,內(nèi)存屏障前的指令也不會(huì)被排到其后,即執(zhí)行內(nèi)存屏障這條指令時(shí),確保其前面的指令已經(jīng)執(zhí)行完畢。
volatile修飾的變量是否具有原子性
volatile只能保證讀取的變量為最新值,無(wú)法保證原子性
比如i++這個(gè)操作,因?yàn)槠洳皇窃硬僮鳎譃樽x取和++兩個(gè)操作,線程1讀取i為1,如果線程1被阻塞住,沒(méi)來(lái)得及++;線程2讀取的也是最新的,結(jié)果也是1,線程2更新i后,i變?yōu)?code>2,線程1繼續(xù)運(yùn)行,由于其工作內(nèi)存中的i還是1,所以導(dǎo)致線程1會(huì)再將i更新為2,這樣就出現(xiàn)問(wèn)題。
使用場(chǎng)景
synchronized本質(zhì)是加鎖,對(duì)性能肯定有影響。所以某些情況下volatile關(guān)鍵字性能是優(yōu)于synchronized的;但是volatile無(wú)法保證原子性,所以無(wú)法替代syschronized。
一般在如下場(chǎng)景下使用volatile關(guān)鍵字:
1)對(duì)變量的寫(xiě)操作不依賴于當(dāng)前值
2)該變量沒(méi)有包含在具有其他變量的不變式中
下面列舉幾個(gè)Java中使用volatile的幾個(gè)場(chǎng)景。
1.狀態(tài)標(biāo)記量
volatile boolean shutdownRequested;
...
public void shutdown() {
shutdownRequested = true;
}
public void doWork() {
while (!shutdownRequested) {
// do stuff
}
}
2.獨(dú)立觀察(independent observation)
定期 “發(fā)布” 觀察結(jié)果供程序內(nèi)部使用
//身份驗(yàn)證機(jī)制如何記憶最近一次登錄的用戶的名字
public class UserManager {
public volatile String lastUser; //發(fā)布的信息
public boolean authenticate(String user, String password) {
boolean valid = passwordIsValid(user, password);
if (valid) {
User u = new User();
activeUsers.add(u);
lastUser = user;
}
return valid;
}
}
3.開(kāi)銷(xiāo)較低的“讀-寫(xiě)鎖”策略
如果讀操作遠(yuǎn)遠(yuǎn)超過(guò)寫(xiě)操作,您可以結(jié)合使用內(nèi)部鎖和 volatile 變量來(lái)減少公共代碼路徑的開(kāi)銷(xiāo)。
public class CheesyCounter {
// Employs the cheap read-write lock trick
// All mutative operations MUST be done with the 'this' lock held
@GuardedBy("this") private volatile int value;
//讀操作,沒(méi)有synchronized,提高性能
public int getValue() {
return value;
}
//寫(xiě)操作,必須synchronized。因?yàn)閤++不是原子操作
public synchronized int increment() {
return value++;
}