volatile到底是個什麼鬼 詳解

2021-10-19 14:20:07 字數 3586 閱讀 1822

先看乙個現象,main執行緒對run變數的修改對於t執行緒不可見,導致了t執行緒無法停止

static boolean run=true;

public static void main(string args) throws interruptedexception

});t.start();

sleep(1);

run=false; //主線程中將run的值改為false,執行緒t中的while迴圈會不會如預想的停止???

}

答案是:不會,主線程中將run的值改為false,執行緒t中的while迴圈不會如預想的停止。

為什麼呢?分析:

1)初始狀態,t執行緒剛開始從主記憶體中讀取了run的值到工作記憶體中。

2)因為t執行緒要頻繁從主記憶體中讀取run的值,jit編譯器會將run的值快取到自己的工作記憶體中的快取記憶體中,減少對主存中run的訪問,提高效率。

3)1秒之後,main執行緒改變了run的值,並同步到主記憶體中,而t執行緒是從自己工作記憶體中的快取記憶體中讀取的run的值,結果永遠是舊的值。

volatile(易變關鍵字):它可以用來修飾成員變數和靜態成員變數,可以避免執行緒從自己的工作快取查詢變數的值,必須從主存中讀取變數的值。

上述例子體現的實際就是可見性,它保證的是在多個執行緒之間,乙個執行緒對volatile變數的修改對另乙個執行緒可見,不能保證原子性,僅用在乙個寫執行緒,多個讀執行緒的情況,上例從位元組碼理解是這樣的:

注意:synchronized語句塊可以保證**塊的原子性和可見性,但是缺點是synchronized是屬於重量級操作,效能相對比較低。

以上是說到了volatile保證了共享變數的可見性,但是volatile還有乙個重要作用就是保證有序性,那麼有序性到底是什麼???

static int i;

static int j;

//在某個執行緒內執行如下賦值操作

i=...;

j=...;

可以看到,無論是先執行i還是先執行j,對最終結果不會產生影響。所以,上面的**在真正執行的時候既可以是

i=...;

j=...;

也可以是

j=...;

i=...;

這種特性稱之為指令重排,多執行緒下的指令重排會影響正確性。但是為什麼會有指令重排這種現象??在不改變程式結果的前提下,這些指令的各個階段可以通過重排序和組合來實現指令級並行,這一技術在80-90年代佔據了計算機架構的重要地位。

int num=0;

boolean ready=false;

//執行緒1執行此方法

public void actor1(i_result r)else

}//執行緒2執行此方法

public void actor2(i_result r)

i_result 是乙個物件,有乙個屬性 r1 用來儲存結果,問,可能的結果有幾種?

情況1:執行緒1先執行,這個時候ready=false,所以進入else分支,結果為1

情況2:執行緒2先執行num=2,但是還沒來得及執行ready=true,執行緒1執行,還是進入else分支,結果為1

情況3:執行緒2執行到ready=true,執行緒1執行,這回進入id分支,結果為4(因為num=2已經執行過了)

但是還有一種情況結果為0!!!!

這種情況下是:執行緒2先執行ready=true,切換到執行緒1,進入if分支,相加為0,再切換為執行緒2執行num=2!!!這個時候是執行緒2在執行actor2()方法中的兩條語句時發生了指令重排。

那麼這種情況的解決方法還是volatile,volatile修飾的變數,可以禁止指令重排!!!!

volatile的底層實現原理是記憶體屏障:

寫屏障保證在該屏障之前的,對共享變數的改動,都同步到主存當中

public void actor2(i_result r)
而讀屏障保證在該屏障之後,對共享變數的讀取,載入的是主存中最新資料

還是那句話,不能解決指令交錯

以著名的double-checked locking單例模式為例

public final class singleton

private static singleton instance=null;

public static singleton getinstance()}}

}}

以上實現的特點是:

在多執行緒環境下,上面的**是有問題的,getinstance()方法中**對應的位元組碼為:

其中也許jvm會優化為:先執行24,再執行21。如果兩個執行緒t1,t2按如下時間序列執行

關鍵在於0:getstatic這行**在monitor控制之外,這個時候t1還沒有完全將構造方法執行完畢,如果在構造方法中執行要很多初始化操作,那麼t2拿到的將是乙個未初始化完畢的單例。

對instance使用volatile修飾即可,可以禁用指令重排,但是要注意在jdk 5以上的版本的volatile才會真正有效。

對instance使用volatile修飾即可:

public final class singleton

private static volatile singleton instance=null;

public static singleton getinstance()}}

}}

讀寫volatile變數的時候會加入記憶體屏障,保證下面兩點:

寫屏障保證在該屏障之前的執行緒對共享變數的改動都同步到主存中

而寫屏障保證在該屏障之後的執行緒對共享變數的讀取,載入的是主存中最新的資料

寫屏障會確保指令重排的時候,不會將寫屏障之前的**排在寫屏障之後

讀屏障會確保指令重排的時候,不會將讀屏障之後的**排在讀屏障之後

更底層的讀寫變數時使用lock指令(鎖匯流排)來確保多核cpu之間的可見性與有序性。

IPU到底是個什麼鬼?

在 i.mx6 應用處理器中,有乙個很重要的單元 ipu image processing unit 影象處理單元。影象處理單元的目標是提供從影象輸入 攝像頭感測器 電視訊號輸入等 到顯示裝置 lcd顯示屏 tv輸出 外部影象處理單元等 端到端的資料流訊號處理的全面支援。ipu庫 ipu libra...

關於Redux到底是個什麼鬼

我們故事的主人公,小明。小明大學剛畢業,擺脫了宿舍的集體生活,自己在外面租了個一室一廳的小公寓住。這是客廳的平面圖 一天小明邀請小馬來家裡做客。小馬說 呀你家的家具擺放位置好奇特!這種通過眼睛看到的視覺效果,就是react。每乙個家具都是乙個component,各種不同的components組成了乙...

作業系統到底是個什麼鬼 (二)

指令的執行 首先說明一點 程式是指令的集合 程式的執行就是按照某種控制流程執行指令的過程。乙個單一指令需要的處理叫做指令週期 乙個指令週期需要兩個步驟 取指週期 執行週期 執行指令的硬體是大名鼎鼎的cpu,cpu看似很複雜,其實很簡單,它的構成無非就是運算器,儲存器再就是控制器,cpu為了方便執行指...