Java中的volatile變量大體上有3條語義,其中2條是針對volatile變量自身而言,另外1條說的是volatile變量對其它變量可見性的影響。
首先我們來看volatile自身的語義:
1,讀volatile變量總是可以讀到任何線程最近一次對該變量的寫入。這意味著java編譯器不會優(yōu)化volatile變量的讀寫,每次對volatile變量的寫入都會寫入主內(nèi)存,每次讀取volatile變量也都會從主內(nèi)存中讀取而不會把該變量暫存在工作內(nèi)存(寄存器)中。這就可以保證如下代碼可以工作:
private volatile boolean flag = false;
//線程1一直執(zhí)行如下循環(huán)等待flag變?yōu)閠rue:
while (flag == false) {
? ? ? ? doSomething();
}
//線程2在某個時刻執(zhí)行執(zhí)行:
flag = true;
也就是說線程2在某時刻設(shè)置flag的值為true后,線程1可以立即感知到。如果flag變量沒有volatile修飾,則線程1在線程2設(shè)置flag為true后不一定能夠感知到,這樣可能導(dǎo)致線程1永遠(yuǎn)跳不出那個循環(huán)。
2,對volatile變量的單個讀或單個寫操作都是原子操作。假如有如下代碼在兩個線程中同時執(zhí)行:
private volatile long count;
//線程1
count = 0x1234567890abcdef;
//線程2
count = 0x1111111122222222;
由于這兩個線程中執(zhí)行的都是單個寫操作,本條語義保證了其原子性,所以count最后的值只可能是0x1234567890abcdef或0x1111111122222222這兩者之一,不可能出現(xiàn)諸如0x1234567822222222這樣的非法值(如果count變量沒有volatile修飾的話,則可能出現(xiàn)這種非法值)。同理,如果不保證讀操作是原子性的,則讀的時候可能讀到非法值,即剛好讀了4個字節(jié),然后中間插入了對該變量的寫入,然后再讀剩下的4個字節(jié)。
重要:這條語義只是說明單個讀單個寫是原子的,并不保證又讀又寫這種復(fù)合操作是原子的。比如 它并不會保證couter++是原子的,因為counter++需要2次訪問內(nèi)存,即首先從內(nèi)存中讀取該值,然后加1,然后把結(jié)果寫入內(nèi)存。所以即使是存在volatile修飾的counter變量我們也不能在多個線程中沒有同步手段的保護(hù)下并發(fā)執(zhí)行counter++。
下面來看volatile變量對其它變量可見性的影響:
3,其它線程在觀察到線程A對volatile變量v的修改之時(后),也一定能夠觀察到線程A對源代碼中位于變量v之前的其它變量的修改。文字表達(dá)可能有點抽象,看下面的代碼:
int a, b;
volatile int c;
//線程1執(zhí)行:
a = 1;
b = 2;
c = 3;
//線程2執(zhí)行
if (c == 3) {
? ? ? ? //使用a和b
}
這段代碼可以保證線程2的if條件為真時,也就是當(dāng)c等于3時,a一定等于1, b一定等2。如果c不是volatile變量,則上述結(jié)論是不一定成立的。
從編程的角度來說,理解上面這幾條語義之后就可以寫出正確使用volatile變量的代碼了。
版權(quán)聲明:本文為原創(chuàng)文章,如需轉(zhuǎn)載,請注明出處。