volatile是如何保證記憶體可見性的

2021-09-26 15:50:40 字數 3246 閱讀 7223

volatile 修飾的變數具備兩種特性:

cpu修改資料,首先是對快取的修改,然後再同步回主存,在同步回主存的時候,如果其他cpu也快取了這個資料,就會導致其他cpu快取上的資料失效(通過嗅探匯流排資料傳播,檢查快取對應的主存位址是否被修改過),這樣,當其他cpu再去它的快取讀取這個資料的時候發現快取已失效,就必須從主存重新獲取

注意:執行緒安全必須保證原子性,可見性,有序性。而volatile只能保證可見性和有序性。

所以基於 volatile 變數的運算的復合操作在併發下不一定是安全的。

理解volatile終極方法:volatile變數的單個讀/寫,看成是使用同乙個鎖對這些單個讀/寫操作做了同步。

class example 

system.out.println("thread1 finish loop,i=" + i);

}public boolean getstop()

public void setstop(boolean flag)

}public class volatileexample

});t1.start();

thread.sleep(1000);

system.out.println("主線程即將置stop值為true...");

example.setstop(true);

system.out.println("主線程已將stop值為:" + example.getstop());

system.out.println("主線程等待執行緒1執行完...");

t1.join();

system.out.println("執行緒1已執行完畢,整個流程結束...");}}

當讀乙個 volatile 變數時,jmm 會把該執行緒對應的本地記憶體置為無效(主存資料有修改後)。執行緒接下來將從主記憶體中讀取共享資料並覆蓋快取

當寫乙個 volatile 變數時,jmm 會把該執行緒,先覆蓋快取,且將共享變數值刷到主記憶體

重排序分為編譯器重排序和處理器重排序。為了實現volatile記憶體語義,jmm會分別限制這兩種型別的重排序型別

在每個volatile寫操作的前面插入乙個storestore屏障。

在每個volatile寫操作的後面插入乙個storeload屏障。

在每個volatile讀操作的後面插入乙個loadload屏障。

在每個volatile讀操作的後面插入乙個loadstore屏障。

版本1:(執行緒不安全的)

public class singleton 

private static singleton instance;

public static singleton getinstance()

return instance;

}}

版本2:(執行緒不安全的)採用同步加鎖的方式:

public class singleton 

private static singleton instance;

public static singleton getinstance()

}return instance;

}}

如果getinstance()方法被多個執行緒頻繁的呼叫,乙個執行緒獲取鎖,進去建立物件。其他執行緒將阻塞掛起。導致程式執行效能的下降。

通過雙重檢查,定來降低同步的開銷。如果第一次檢查instance不為null,那麼就不需要執行下面的加鎖和初始

化操作。因此,可以大幅降低synchronized帶來的效能開銷

版本3:(執行緒不安全的)

雙重檢查

//雙重檢測

public class singleton

private static singleton instance;

public static singleton getinstance()}}

return instance;

}}

第二次檢查的作用:在於當執行緒兩個執行緒 a、b都通過了第一次的 if (instance == null) 的判斷的情況。

流程:假設有兩個執行緒 a、b 同時呼叫 getinstance() 方法,他們會同時發現 instance == null ,於是同時對 singleton.class 加鎖,此時 jvm 保證只有乙個執行緒能夠加鎖成功(假設是執行緒 a),另外乙個執行緒則會處於等待狀態(假設是執行緒 b);執行緒 a 會建立乙個 singleton 例項,之後釋放鎖,鎖釋放後,執行緒 b 被喚醒,執行緒 b 再次嘗試加鎖,此時是可以加鎖成功的,加鎖成功後,執行緒 b 檢查 instance == null 時會發現,已經建立過 singleton 例項了,所以執行緒 b 不會再建立乙個 singleton 例項。

看似流程沒問題,實際不堪一擊。因為忽略了指令重拍。

instance = new singleton();操作並不是乙個原子性指令,會被分為多個指令:

發生指令重排後:

所以a執行緒執行完重排後的第二步,還未執行初始化物件的時候。b執行緒就來取instance時,發現instance不為空,於是便直接返回該值,結果便返回null.

版本4:(執行緒安全的)

volatile禁止指令重排。

public class singleton 

private volatile static singleton instance;

static singleton getinstance()

}return instance;

}}

懶漢變餓漢式:(執行緒安全的)instance = new singleton();一樣會指令重排,但對於非構造執行緒是不可見的。

public class singleton 

public static singleton instance = new singleton();

public static instance getinstance()

}

Volatile如何保證可見性

首先要知道記憶體屏障是什麼,記憶體屏障是乙個cpu指令,記憶體屏障是這樣的指令 1,確保特定操作執行的順序 2,影響一些資料的可見性,編譯器和cpu可以保證輸出結果一樣的前提下對指令進行重排序,使得效能優化,當插入乙個記憶體屏障,相當於告訴cpu和編譯器,先於這個命令的必須先執行,後於這個命令的必須...

volatile 關鍵字的如何保證記憶體可見性

volatile關鍵字的作用 保證記憶體的可見性 防止指令重排 注意 volatile 並不保證原子性 記憶體可見性 volatile保證可見性的原理是在每次訪問變數時都會進行一次重新整理,因此每次訪問都是主記憶體中最新的版本。所以volatile關鍵字的作用之一就是保證變數修改的實時可見性。當且僅...

volatile關鍵字如何保證記憶體可見性

一 記憶體可見性 例子1 有乙個全域性的狀態變數open boolean open true 這個變數用來描述對乙個資源的開啟關閉狀態,true表示開啟,false表示關閉,假設有乙個執行緒a,在執行一些操作後將open修改為false 執行緒a resource.close open false ...