若閱讀過程中出現疑問,可先閱讀併發學習總覽
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的操作,...