Java 語(yǔ)言中的volatile
變量可以被看作是一種 “程度較輕的 synchronized
”;與 synchronized
塊相比,volatile 變量所需的編碼較少,并且運(yùn)行時(shí)開(kāi)銷(xiāo)也較少,但是它所能實(shí)現(xiàn)的功能也僅是 synchronized
的一部分。
鎖和volatile
鎖提供了兩種主要特性:原子性和可見(jiàn)性与斤。
原子性即一次只允許一個(gè)線(xiàn)程持有某個(gè)特定的鎖,一次就只有一個(gè)線(xiàn)程能夠使用共享數(shù)據(jù)荚恶×么可見(jiàn)性是必須確保釋放鎖之前對(duì)共享數(shù)據(jù)做出的更改對(duì)于隨后獲得該鎖的另一個(gè)線(xiàn)程是可見(jiàn)的 。
Volatile 變量具有 synchronized 的可見(jiàn)性特性谒撼,但是不具備原子特性食寡。
當(dāng)一個(gè)變量定義為 volatile 之后,將具備:
1.保證此變量對(duì)所有的線(xiàn)程的可見(jiàn)性廓潜,當(dāng)一個(gè)線(xiàn)程修改了這個(gè)變量的值抵皱,volatile 保證了新值能立即同步到主內(nèi)存,其它線(xiàn)程每次使用前立即從主內(nèi)存刷新辩蛋。但普通變量做不到這點(diǎn)呻畸,普通變量的值在線(xiàn)程間傳遞均需要通過(guò)主內(nèi)存來(lái)完成。
2.禁止指令重排序優(yōu)化悼院。有volatile修飾的變量伤为,賦值后多執(zhí)行了一個(gè)“l(fā)oad addl $0x0, (%esp)”操作,這個(gè)操作相當(dāng)于一個(gè)內(nèi)存屏障(指令重排序時(shí)不能把后面的指令重排序到內(nèi)存屏障之前的位置)据途。
我們通過(guò)有序性來(lái)詳細(xì)看下指令重排序绞愚。
指令重排序
有序性:即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行叙甸。舉個(gè)簡(jiǎn)單的例子,看下面這段代碼:
int i = 0;
boolean flag = false;
i = 1; //語(yǔ)句1
flag = true; //語(yǔ)句2
從代碼順序上看位衩,語(yǔ)句1是在語(yǔ)句2前面的裆蒸,那么JVM在真正執(zhí)行這段代碼的時(shí)候會(huì)保證語(yǔ)句1一定會(huì)在語(yǔ)句2前面執(zhí)行嗎?不一定蚂四,為什么呢光戈?這里可能會(huì)發(fā)生指令重排序(Instruction Reorder)。
指令重排序:
一般來(lái)說(shuō)遂赠,處理器為了提高程序運(yùn)行效率,可能會(huì)對(duì)輸入代碼進(jìn)行優(yōu)化晌杰,它不保證程序中各個(gè)語(yǔ)句的執(zhí)行先后順序同代碼中的順序一致跷睦,但是它會(huì)保證程序最終執(zhí)行結(jié)果和代碼順序執(zhí)行的結(jié)果是一致的。
比如上面的代碼中肋演,語(yǔ)句1和語(yǔ)句2誰(shuí)先執(zhí)行對(duì)最終的程序結(jié)果并沒(méi)有影響抑诸,那么就有可能在執(zhí)行過(guò)程中,語(yǔ)句2先執(zhí)行而語(yǔ)句1后執(zhí)行爹殊。雖然處理器會(huì)對(duì)指令進(jìn)行重排序蜕乡,但是它會(huì)保證程序最終結(jié)果會(huì)和代碼順序執(zhí)行結(jié)果相同,那么它靠什么保證的呢梗夸?靠的是數(shù)據(jù)依賴(lài)性:
編譯器和處理器在重排序時(shí)层玲,會(huì)遵守?cái)?shù)據(jù)依賴(lài)性,編譯器和處理器不會(huì)改變存在數(shù)據(jù)依賴(lài)關(guān)系的兩個(gè)操作的執(zhí)行順序反症。
舉例如下代碼
double pi = 3.14; //A
double r = 1.0; //B
double area = pi * r * r; //C
上面三個(gè)操作的數(shù)據(jù)依賴(lài)關(guān)系如下圖所示
A和C之間存在數(shù)據(jù)依賴(lài)關(guān)系辛块,同時(shí)B和C之間也存在數(shù)據(jù)依賴(lài)關(guān)系。因此在最終執(zhí)行的指令序列中铅碍,C不能被重排序到A和B的前面(C排到A和B的前面润绵,程序的結(jié)果將會(huì)被改變)。但A和B之間沒(méi)有數(shù)據(jù)依賴(lài)關(guān)系胞谈,編譯器和處理器可以重排序A和B之間的執(zhí)行順序尘盼。下圖是該程序的兩種執(zhí)行順序:
在計(jì)算機(jī)中,軟件技術(shù)和硬件技術(shù)有一個(gè)共同的目標(biāo):在不改變程序執(zhí)行結(jié)果的前提下烦绳,盡可能的開(kāi)發(fā)并行度卿捎。編譯器和處理器都遵從這一目標(biāo)。
這里所說(shuō)的數(shù)據(jù)依賴(lài)性?xún)H針對(duì)單個(gè)處理器中執(zhí)行的指令序列和單個(gè)線(xiàn)程中執(zhí)行的操作爵嗅,在單線(xiàn)程程序中娇澎,對(duì)存在控制依賴(lài)的操作重排序,不會(huì)改變執(zhí)行結(jié)果睹晒;但在多線(xiàn)程程序中趟庄,對(duì)存在控制依賴(lài)的操作重排序括细,可能會(huì)改變程序的執(zhí)行結(jié)果。這是就需要內(nèi)存屏障來(lái)保證可見(jiàn)性了戚啥。
內(nèi)存屏障
內(nèi)存屏障分為兩種:Load Barrier 和 Store Barrier即讀屏障和寫(xiě)屏障奋单。
內(nèi)存屏障有兩個(gè)作用:
1.阻止屏障兩側(cè)的指令重排序;
2.強(qiáng)制把寫(xiě)緩沖區(qū)/高速緩存中的臟數(shù)據(jù)等寫(xiě)回主內(nèi)存猫十,讓緩存中相應(yīng)的數(shù)據(jù)失效览濒。
- 對(duì)于Load Barrier來(lái)說(shuō),在指令前插入Load Barrier拖云,可以讓高速緩存中的數(shù)據(jù)失效贷笛,強(qiáng)制從新從主內(nèi)存加載數(shù)據(jù);
- 對(duì)于Store Barrier來(lái)說(shuō)宙项,在指令后插入Store Barrier乏苦,能讓寫(xiě)入緩存中的最新數(shù)據(jù)更新寫(xiě)入主內(nèi)存,讓其他線(xiàn)程可見(jiàn)尤筐。
java的內(nèi)存屏障通常所謂的四種即LoadLoad,StoreStore,LoadStore,StoreLoad實(shí)際上也是上述兩種的組合汇荐,完成一系列的屏障和數(shù)據(jù)同步功能。
LoadLoad屏障:對(duì)于這樣的語(yǔ)句Load1; LoadLoad; Load2盆繁,在Load2及后續(xù)讀取操作要讀取的數(shù)據(jù)被訪問(wèn)前掀淘,保證Load1要讀取的數(shù)據(jù)被讀取完畢。
StoreStore屏障:對(duì)于這樣的語(yǔ)句Store1; StoreStore; Store2油昂,在Store2及后續(xù)寫(xiě)入操作執(zhí)行前革娄,保證Store1的寫(xiě)入操作對(duì)其它處理器可見(jiàn)。
LoadStore屏障:對(duì)于這樣的語(yǔ)句Load1; LoadStore; Store2秕狰,在Store2及后續(xù)寫(xiě)入操作被刷出前稠腊,保證Load1要讀取的數(shù)據(jù)被讀取完畢。
StoreLoad屏障:對(duì)于這樣的語(yǔ)句Store1; StoreLoad; Load2鸣哀,在Load2及后續(xù)所有讀取操作執(zhí)行前架忌,保證Store1的寫(xiě)入對(duì)所有處理器可見(jiàn)。它的開(kāi)銷(xiāo)是四種屏障中最大的我衬。在大多數(shù)處理器的實(shí)現(xiàn)中叹放,這個(gè)屏障是個(gè)萬(wàn)能屏障,兼具其它三種內(nèi)存屏障的功能
volatile
的內(nèi)存屏障策略非常嚴(yán)格保守挠羔,非常悲觀且毫無(wú)安全感的心態(tài):
在每個(gè)volatile寫(xiě)操作前插入StoreStore屏障井仰,在寫(xiě)操作后插入StoreLoad屏障;
在每個(gè)volatile讀操作前插入LoadLoad屏障破加,在讀操作后插入LoadStore屏障俱恶;
由于內(nèi)存屏障的作用,避免了volatile變量和其它指令重排序、線(xiàn)程之間實(shí)現(xiàn)了通信合是,使得volatile表現(xiàn)出了鎖的特性了罪。
volatile 性能:
volatile 的讀性能消耗與普通變量幾乎相同,但是寫(xiě)操作稍慢聪全,因?yàn)樗枰诒镜卮a中插入許多內(nèi)存屏障指令來(lái)保證處理器不發(fā)生亂序執(zhí)行泊藕。