一文搞懂volatile的可見性原理

2022-09-11 16:30:30 字數 2428 閱讀 1299

說volatile之前,了解jmm(j**a記憶體模型)有助於我們理解和描述volatile關鍵字。jmm是j**a虛擬機器所定義的一種抽象規範,用來遮蔽不同硬體和作業系統的記憶體訪問差異,讓j**a程式在各種平台下都達到一致的記憶體訪問效果。jmm也可以稱之為j**a執行緒記憶體模型,也描述了j**a執行緒在工作中對資料的操作過程以及描述了執行緒之間的通訊過程。

以上便是jmm的基本邏輯圖,j**a採用工作記憶體和主記憶體進行資料互動的原因可以解釋為,工作記憶體一般為cpu的快取記憶體,cpu的快取記憶體就是為了解決cpu日益增長的速度與主存不匹配導致浪費計算資源,所以執行緒的工作記憶體位於cpu的快取記憶體中來提高運算速度。但是多個執行緒在對主記憶體中共享變數操作時會有乙個可見性問題。具體可看以下**:

package

xyz.ring2.demo.test;

public

class

volatilevisibilitytest

public

static

void main(string args) throws

interruptedexception

}}).start();

thread.sleep(200);

new thread(new

runnable()

}).start();

thread.sleep(200);

system.out.println("work done.");}}

在該程式中有乙個共享變數flag,第乙個執行緒執行時等待別的執行緒改變flag的值使其跳出迴圈,第二個執行緒是去改變共享變數flag的值。在我們看來,第乙個執行緒只需要等待第二個執行緒改變了flag的即可跳出迴圈。以下是程式執行結果:

可以看到當「work done」列印出來時程式還沒有停止,此時我們可以得出結論。兩個執行緒對共享變數的操作是互相不可見的。此時我們很自然的想到了通過加synchronizedj**a內建鎖來解決。

通過在while迴圈外新增synchronized(this)同步塊確實能解決這種問題,但是在這種僅僅只需要保證乙個共享變數可見的情況下採用synchronized鎖來保證同步代價太大,此時我們應該採用j**a所

提供的volatile關鍵字來保證變數的可見性。使用上通過在flag前加上volatile關鍵字即可。

public

static

volatile

boolean flag = false;

以下是執行結果:

正常的使程式結束了,執行緒一成功的感知到了執行緒二對flag變數的改變。

那麼volatile關鍵字使如何保證多執行緒下共享變數執行緒間可見的呢?

首先我們來了解以下jmm中的資料原子操作:

jvm通過以上原子操作來處理主記憶體和工作記憶體中的資料互動。那麼volatile到底是如何保證的呢?

j**a中的volatile關鍵字是通過呼叫c語言實現的,而在更底層的實現上,即組合語言的層面上,用volatile關鍵字修飾後的變數在操作時,最終解析的彙編指令會在指令前加上lock字首指令

來保證工作記憶體中讀取到的資料是主記憶體中最新的資料。具體的實現原理是在硬體層面上通過:mesi快取一致性協議:多個cpu從主記憶體讀取資料到快取記憶體中,如果其中乙個cpu修改了資料

,會通過匯流排立即回寫到主記憶體中,其他cpu會通過匯流排嗅探機制感知到快取中資料的變化並將工作記憶體中的資料失效,再去讀取主記憶體中的資料。

ia32架構軟體開發者手冊對lock字首指令的解釋:

1.會將當預處理器快取行的資料立即回寫到系統記憶體中,

2.這個寫回記憶體的操作會引起其他cpu裡快取了該記憶體位址的資料失效(mesi協議)

現在我們知道了volatile可以保證變數的可見性,我們還應該知道volatile不可以保證原子性:

volatile無法保證原子性:如:兩個執行緒同時read主記憶體中相同的值,load到工作記憶體中,兩個執行緒的cpu又同時use了count值並進行了計算且assign回工作記憶體,但其中乙個執行緒通過匯流排store回主記憶體的

速度更快,於是由於(匯流排)mesi快取一致性協議下的cpu匯流排嗅探機制就會使得另乙個執行緒工作記憶體中的變數副本失效,導致之前的操作結果丟失(可以結合理解)。

可以讀取我的另一篇文章:單例模式值雙檢索 來理解一下重排序所帶來的問題。

一文搞懂transform skew

目錄 如何理解斜切 skew,先看乙個 demo。在下面的 demo 中,有 4 個正方形,分別是 紅色 不做 skew 變換,綠色 x 方向變換,藍色 y 方向變換,黑色 兩個方向都變換,拖動下面的滑塊可以檢視改變 skew 角度後的效果。切換 selector 可以設定 transform or...

一文搞懂property函式

接下來我帶大家了解乙個函式的作用以及使用技巧,希望對大家都有幫助,話不多說,接下來就開始我的表演特性 首先property有兩種用法,一種是作為函式的用法,一種是作為裝飾器的用法,接下來我們就逐一分析 property函式 看一下作為函式它包含的引數都有哪些 property fget none,f...

一文搞懂記憶體屏障

gcc編譯選項中有個 o選項,表示編譯 的時候進行優化。這樣就會出現一種可能 優化後的 和優化前的 順序不一致。來看個例子 8 include9 10 int a,b 11 12 int main 13 很簡單,我們使用不加優化選項來將其編譯為組合語言 yuhao laplace workspace...