深入學習併發之二 volatile關鍵字詳解

2021-09-02 22:03:32 字數 3542 閱讀 3023

若閱讀過程中出現疑問,可先閱讀併發學習總覽

volatile滿足了併發中的原子性、可見性和區域性有序性,但是其中的原子性是存在侷限性的。

volatile原子性的侷限:volatile變數的寫和volatile的讀都是有原子性的,但是由於其實現方式並不是使用的同步的思想,所以並不能獨佔時間片。這也導致了諸如volatile變數的自增自減操作並沒有原子性。

volatile區域性有序性:在底層實現中,volatile變數的讀寫 以及 其前後的普通變數讀寫操作需要滿足一定的有序性。

volatile寫之前的寫操作,和之後的讀操作不能重排序;

volatile讀之後的讀寫操作都不能重排序。

volatile變數的讀寫操作都是有原子性的,這是因為jmm對volatile變數的讀寫操作,進行了特殊規則的規定。原子性操作的原理一般是加鎖或者迴圈cas,但是volatile的原子性原理,並非這兩種中的任何一種。

先看一下jmm中的一些原子操作:

主存工作記憶體

執行緒read

use讀

write

assign

read—>use—>讀

write<—assign<—寫

read:作用在主存的變數中,將主存中的變數內容更新到工作記憶體中

use:作用在工作記憶體的變數中,將工作記憶體中的變數內容更新到執行緒中

write:作用在工作記憶體變數中,將執行緒中的變數賦給工作記憶體中的變數

assign:作用在主存變數中,將工作記憶體的變數寫入主存

在讀任何變數時,都會在工作記憶體中使用use操作,將變數從工作變數中讀入執行緒中;但是對volatile變數,必須在呼叫use前先呼叫read,先從主存中將對應volatile變數取出,傳入工作記憶體,再將工作記憶體中的變數取到執行緒中。

同理,在寫任何變數時,都會在工作記憶體中使用assign操作,將變數寫入工作記憶體中;但是對volatile變數來說,必須在assign後立即在主存中write,將變數寫入主存中。

這樣,volatile變數的讀寫就是具備原子性的了。

但是由於volatile變數實現原子性的方式,是對其讀寫操作順序的限制所以在諸如volatile++這樣的多位元組碼(機器碼)操作中,並不能保證它的原子性

volatile的可見性是最好理解的,就是通過執行緒間的共享記憶體——主存,來實現執行緒間的通訊的。

在開始解釋volatile是怎麼實現有序性前,先做一些設定

//假設在主存中有這樣的變數

//其中,flag是會被多個執行緒訪問到的volatile變數;

// count是會被多個執行緒訪問到的共享變數,但並不是volatile的

volatile int flag;

int count;

區域性有序性,即對volatile讀寫操作前後的讀寫操作進行一些約束。

記憶體屏障:storestore、storeload、loadstore及loadload屏障

storestore屏障的前一句寫操作,和其後一句寫操作不可交換位置;

storeload屏障的前一句寫操作,和其後一句讀操作不可交換位置;

loadstore屏障的前一句讀操作,和其後一句寫操作不可交換位置;

loadload屏障的前一句讀操作,和其後一句操作不可交換位置;

下面具體分析一下哪些語句需要加入哪些屏障:

volatile寫/讀

前的屏障

後的屏障

寫storestore

storeload讀無

loadstore,loadload

volatile寫之前的寫操作,和之後的讀操作不能重排序;

volatile讀之後的讀寫操作都不能重排序。

volatile寫是將所有工作記憶體中的共享變數寫入主存

普通寫操作是將執行緒內的共享變數寫入工作記憶體,如果將volatile寫前的普通寫,重排序到其後,本來應該更新到主存內的共享變數,就不會更新到主存中,所以要加入storestore屏障。如**段1:

//**段1

//volatile寫前的普通寫,若重排序,count不能更新到主存中

count = 1; //普通寫

flag = 1; //volatile寫

在volatile寫之後的volatile讀,很好理解,本應先更新再讀取,不可先讀取再更新。所以要在其之後加入storeload屏障。

volatile讀是將所有共享變數由主存更新至工作記憶體

普通讀操作是將共享變數從工作記憶體讀入執行緒中,如果將volatile讀之後的普通讀,重排序到其前,那麼本該讀到最新資料的共享變數,就讀到了舊資料,所以應加入volatile讀後的loadload屏障。如**段2:

//**段2

//volatile讀後的普通讀,若重排序,會讀到count的舊資料

int flagnow = flag; //volatile讀

int countnow = count; //普通讀

普通寫操作語義見上述內容,若將volatile讀後的普通寫,重排序到其前,則其更新到工作記憶體中的共享變數會被volatile讀覆蓋,無法在接下來的操作中使用,所以volatile讀後需要加入loadstore屏障。如**段3

//**段3

//volatile讀後的普通寫,若重排序,最後寫的普通共享變數會被覆蓋

int flagnow = flag; //volatile讀

count = 1; //普通寫

綜上所述,

volatile寫前應加入storestore屏障,防止普通共享變數的寫入不被更新;

volatile寫後應加入storeload屏障,防止volatile變數讀寫順序出現問題;

volatile讀後應加入loadload屏障,防止普通共享變數讀到舊資料;

volatile讀後應加入loadstore屏障,防止普通共享變數的寫入被覆蓋;

1 volatile滿足了讀寫的原子性,可見性以及區域性有序性

2 volatile通過繫結寫入和讀取變數的「執行緒—工作記憶體」和「工作記憶體—主存」兩段操作實現了讀寫的原子性。

3 volatile通過讀寫時,將共享變數一併讀取/寫入到工作記憶體/主存的方式,實現了可見性。

4 volatile通過在讀寫語句前後加入記憶體屏障,實現了區域性有序性。

深入學習ajax系列之二 請求方式

最常見的請求莫過於get和post了,今天詳細的學習一下兩種方式的內容,get get是常見的請求方式,常用於向伺服器查詢某些資訊,它適用於url完全指定資源,當請求對伺服器沒有任何 以及伺服器的響應式可快取的。資料傳送 使用get的方式傳送請求時,資料被追加到open 方法中url的末尾 資料以問...

Java併發系列五 深入理解volatile關鍵字

instance new instancce instance是volatile變數 這個寫回記憶體的操作會使得其他cpu裡快取了該記憶體位址的資料無效 乙個處理器的快取回寫到記憶體會導致其他處理器的快取失效 當處理器發現本地快取失效後,就會從記憶體中重讀該變數資料,即可以獲取當前最新值。publi...

深入學習Java併發之一 併發學習總覽

重排序 為了提高程式處理效能 編譯器和處理器可能會對 執行順序進行亂序執行 int i 5 1 int k 1 2 int j 5 3 i 10 4 k i 5在如上 片中,1 2和3可以進行重排序。因為1和2都被賦值為了5,在機器碼層面上,可以取出5這個值,依次賦給i和j,再執行給k賦為1的操作,...