Java併發-volatile和synchronized
阿新 • • 發佈:2018-12-13
volatile
功能
- 保證多執行緒的可見性
- 禁止一部分的重排序。
- volatile 是輕量級的synchronized
- 對任意單個的volatile的讀/寫是原子性的(volatile=1/return volatile),但是複合型操作不支援。(volatile++操作)
特性
對於volatile來說具有以下特性
1.volatile標記的變數在讀操作的時候,一定是最新的值。
為什麼?
1、lock字首的支援,volatile規定每次修改操作必須重新整理到記憶體,讀操作也要到記憶體中去取新值。
2、匯流排的支援,多條匯流排事務同一時刻同一時刻只有一條能獲得訪問記憶體的許可權,因此,只要有執行緒修改了,那麼讀操作一定取到的是新值。
####記憶體語意
讀:當讀一個volatile變數時,JMM會把該執行緒對應的本地記憶體置為無效。執行緒接下來將從主記憶體中讀取共享變
寫:當寫一個volatile變數時,JMM會把該執行緒對應的本地記憶體中的共享變數值重新整理到主內
原理
instance = new Singleton(); // instance是volatile變數
0x01a3de1d: movb $0×0,0×1104800(%esi);0x01a3de24: **lock** addl $0×0,(%esp);
lock字首的指令在多核處理器下會觸發兩種操作
- 將當前處理器快取行的資料寫回到系統記憶體。
- 這個寫回記憶體的操作會使在其他CPU裡快取了該記憶體地址的資料無效。
依賴CPU的快取一致性的支援
volatile規則表
當第二個操作是volatile寫時,不管第一個操作是什麼,都不能重排序。這個規則確保volatile寫之前的操作不會被編譯器重排序到volatile寫之後。
當第一個操作是volatile讀時,不管第二個操作是什麼,都不能重排序。這個規則確保volatile讀之後的操作不會被編譯器重排序到volatile讀之前。
當第一個操作是volatile寫,第二個操作是volatile讀時,不能重排
在每個volatile寫操作的前面插入一個StoreStore屏障。
在每個volatile寫操作的後面插入一個StoreLoad屏障。
在每個volatile讀操作的後面插入一個LoadLoad屏障。
在每個volatile讀操作的後面插入一個LoadStore屏障。
synchronized
- 原子性。
- 由於執行緒同步,某一時刻,只能一個執行緒操作共享變數,也就保證了多執行緒間的可見性
使用
- 普通同步方法:鎖是this鎖
- 程式碼塊:鎖是該物件鎖
- 靜態同步方法:鎖是當前Class物件鎖
原理
- 都是使用Monitor(監視器)物件來操作的
monitorenter在編譯時插入到synchronized的開始位置,monitorexit在編譯時插入在synchronized的結束位置和者異常結束的位置。 - 任何物件都是鎖物件,那麼任何物件都與一個monitor相關聯,當獲取到monitor,該monitor就會處於鎖定狀態。當指令執行到monitorenter時,就會去嘗試獲取該monitor的所有權。
原子性
- 通過鎖來實現一些列操作的原子性。但是效能差,會造成阻塞,增加效能消耗。
- 通過CAS(compare and swap)來實現,此實現針對於賦值操作。i++,
- 通過E(期望值,也就是快取/本地記憶體/工作記憶體的副本),V(當前值)來操作。通過E和主記憶體的值進行比較,如果相等的話,就會將新值賦值給當前值。
- 問題,會造成ABA的問題,當其他執行緒將共享變數的值變換為B,然後再該為A,那麼該執行緒去將本地記憶體的值刷到主記憶體的時候,將會執行成功。但是結果可能是錯的。
- 通過CAS+version的方式來解決ABA的問題。
- 也就是,共享變數V的值為 1A->2B->3A 那麼通過此版本號去控制,就不會巧妙的去除ABA問題。
- 注意,CAS只能處處理一個變數,如果多個可以使用ij = 1a的方式,AtomicReference保證引用物件的原子性。
- CAS 不需要新增鎖,但是迴圈驗證會增加CPU的消耗。
核心程式碼
- CAS實現操作,CASXXXX方法就是CAS操作。Unsafe.class 操作c程式碼。
do{
N = E+1;
}while(CASXXXX((E,N));
java concurrent包下的atomic類
AtomicInteger AtomicBoolean...(CAS,會出現BAB的問題)
AtomicStampedReference 解決了CAS的ABA的問題