java中的volatile關(guān)鍵字的特性及作用

序言


volatile關(guān)鍵字的特性及作用


想要理解volatile關(guān)鍵字的作用,需要先對jvm中的內(nèi)存模型有所了解。
Java內(nèi)存模型規(guī)定,對于多個線程共享的變量,存儲在主內(nèi)存當中,每個線程都有自己獨立的工作內(nèi)存(比如CPU的寄存器),線程只能訪問自己的工作內(nèi)存,不可以訪問其它線程的工作內(nèi)存。工作內(nèi)存中保存了主內(nèi)存共享變量的副本,線程要操作這些共享變量,只能通過操作工作內(nèi)存中的副本來實現(xiàn),操作完畢之后再同步回到主內(nèi)存當中。
對jvm的內(nèi)存模型有了基本的了解后,再來看volatile關(guān)鍵字的幾個重要特性

內(nèi)存一致性


image.png

假設(shè)現(xiàn)在有兩個工作線程A和B以及一個控制工作流的標志位flag

Thread A;
Thread B;
boolean flag = true;

A線程中對flag做了一些修改(將flag置為false),但此時Thread A只是再修改線程私有的工作內(nèi)存,ThreadB看不到這個修改,那么此時ThreadB依賴標志位的一些邏輯就將變得不再可靠.

Thread A;
Thread B;
volatile boolean flag = true;

但如果將標志位用volatile關(guān)鍵字來修飾,ThreadA再修改標志位flag的時候從主內(nèi)存中刷新變量的最新值,同時將線程B工作內(nèi)存中的flag變量置為不可靠狀態(tài)(dirty),那么下次ThreadB如果要使用flag標志位的時候就會從主內(nèi)存中讀取變量的最新值,從而保證了變量再不同線程中的一致性.

防止指令重排

先看下jvm中指令重排的定義:
指令重排序是JVM為了優(yōu)化指令,提高程序運行效率,在不影響單線程程序執(zhí)行結(jié)果的前提下,盡可能地提高并行度。編譯器、處理器也遵循這樣一個目標。注意是單線程。多線程的情況下指令重排序就會給程序員帶來問題。

關(guān)于指令重排,可以通過單例模式的經(jīng)典模式雙重加鎖來詳細了解:

if(mInstance==null){
  synchronized(mObject){
    mInstance = new Instance();
}
}
return mInstance;

雙重加鎖單例模式是一種懶漢式的加載模式,為了減少鎖的消耗會再函數(shù)入口處提前判空,再在鎖的代碼段落內(nèi)初始化實例。但實際上初始化的代碼并非原子操作:

mInstance = new Instance()

它是有三條指令組合而成的:

memory =allocate();    //1:分配對象的內(nèi)存空間 
ctorInstance(memory);  //2:初始化對象
instance =memory;     //3:instance指向剛分配的內(nèi)存地址,此時對象還未初始化

如果jvm對這三條指令進行指令重排,比如按照 1-3-2 的順序執(zhí)行,那么在下面這種場景下就會產(chǎn)生問題:

ThreadA調(diào)用 getInstance()方法執(zhí)行完了1和3兩條指令,此時對象未初始化但是mIntance已經(jīng)指向了一塊內(nèi)存區(qū)域;
ThreadB此時進入getIntance()方法,判定mIntance引用不為空,直接返回。
ThreadB就會使用到未初始化的實例對象,產(chǎn)生不可預(yù)期的錯誤.

volatile關(guān)鍵字通過禁止指令重排來避免了這種問題的產(chǎn)生.

內(nèi)存屏障


上面總結(jié)到volatile關(guān)鍵字可以實現(xiàn)變量在各線程中的一致性,并且具有禁止指令重排的功能。其實這兩個特性是通過內(nèi)存屏障來實現(xiàn)的.
內(nèi)存屏障是jvm上的指令,jvm上還有其它指令例如:

(1) lock:將主內(nèi)存中的變量鎖定,為一個線程所獨占

(2) unclock:將lock加的鎖定解除,此時其它的線程可以有機會訪問此變量

(3) read:將主內(nèi)存中的變量值讀到工作內(nèi)存當中

(4) load:將read讀取的值保存到工作內(nèi)存中的變量副本中。

(5) use:將值傳遞給線程的代碼執(zhí)行引擎

(6) assign:將執(zhí)行引擎處理返回的值重新賦值給變量副本

(7) store:將變量副本的值存儲到主內(nèi)存中。

(8) write:將store存儲的值寫入到主內(nèi)存的共享變量當中。

從功能上內(nèi)存屏障可以分為兩種:

  • 讀障礙:在某條指令前插入讀障礙指令,保證從主內(nèi)存中讀取最新值.
  • 寫障礙:在某條指令后插入寫障礙指令,保證將緩沖區(qū)工作內(nèi)存中的值寫入到主內(nèi)存.

而內(nèi)存屏障指令具體可分為四種:

loadload: 在load1和load2指令之間插入,保障在執(zhí)行l(wèi)oad2之前l(fā)oad1指令的讀取操作完成.
storestore: 在store1和store2指令之間插入,保障在執(zhí)行store2之前store1指令的寫入操作對其它線程(處理器)可見.
loadstore: 在load1和store2指令之間插入,保障在執(zhí)行store2之前l(fā)oad1指令的讀取操作完成.
storeload 在store1和load2指令之間插入,保障在執(zhí)行l(wèi)oad2之前store1指令的寫入操作完成并對其它線程(處理器)可見.

volatile關(guān)鍵字的注意事項


volatile關(guān)鍵字使得對于單個變量的讀寫操作具有了原子性,但不包括自增自減這種操作。也就是說

volatile a;
a = 1;
b = a;

在語義上等同于

sychronized(){
  a = 1;
}

synchronized(){
  b =a;
}

volatile a;
a++;

這種操作并不具有原子性,因為a++并非一條指令!,當翻譯成jvm上的機器碼時,它會變成若干條指令.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容