《JAVA併發程式設計實戰》原子變數和非阻塞同步機制
引言
即使原子變數沒有用於非阻塞演算法的開發,他們也可以用作一種“更好的”volatile型別變數。原子變數提供了與volatile型別變數相同的記憶體語義,此外還支援原子的更新操作,從而使他們更加適用於實現計數器、序列發生器和統計資料收集等,同時還能比基於鎖的方法提供更高的可伸縮性。
鎖的劣勢
通過使用一致的鎖定協議來協調對共享狀態的訪問,可以確保無論哪個執行緒持有守護變數的鎖,都能採用獨佔方式來訪問這些變數,並且對變數的任何修改對隨後獲得這個鎖的其他執行緒都是可見的。
如果多個執行緒同時請求鎖,那麼一些執行緒將被掛起並且在稍後恢復執行。當執行緒恢復執行時,必須等待其他執行緒執行完他們的時間片以後,才能被排程執行。在掛起和恢復執行緒等過程中存在著很大的開銷,並且通常存在著較長時間的中斷。
volatile通過較輕量級的同步機制,因為在使用這些變數時不會發生上下文切換或執行緒排程等操作。然而,volatile變數只能保證可見性,而不能保證原子性。
鎖定還有一些缺點,主要表現在上下文切換,排程延遲等。
硬體對軟體的支援
獨佔式是一項悲觀技術——它假設了最壞的情況(如果你不鎖門,你就會被偷),並且只有在確保其他執行緒不會造成干擾的情況下才能執行下去。
一種樂觀的方法:通過這種方法可以在不發生干擾的情況下完成更新操作。這種方法需要藉助衝突檢查機制來判斷在更新過程中是否存在來自其他執行緒的干擾,如果存在,則這個操作將失敗並且可以重試。
現代處理器支援了一些原子的寫指令:比較並交換(Compare and swap)或者關聯載入/條件儲存。
比較並交換
CAS包含了3個運算元——需要讀寫的記憶體為止V、進行比較的值A、擬寫入的新值B。當且僅當V的值等於A時,CAS才會通過原子方式用新值B來更新V,否則不執行任何操作。
模擬CAS
public class SimulatedCAS{
private int value;
public synchronized int get(){
return value;
}
public synchronized int compareAndSwap(int expectedValue,int newValue){
int oldValue = value;
if(oldValue == newValue){
value = newValue;
}
return oldValue;
}
public synchronized boolean compareAndSet(int expectedValue,int newValue){
return (expectedValue == compareAndSwap(expectedValue,newValue);
}
}
非阻塞的計數器
public class CasCounter{
private SimulatedCAS value;
public int getValue(){
return value.get();
}
public int increment(){
int v;
do{
v = value.get();
}while(v != value.compareAndSwap(v,v+1));
return v+1;
}
}
當競爭不高時,基於CAS的計數器在效能上遠超過了基於鎖的計數器,而在沒有競爭的時候更高。
CAS的主要缺點是,它將使呼叫者處理競爭問題(通過重試、回退、放棄),而在鎖中能自動處理競爭問題(執行緒在獲得鎖之前一直阻塞)。
原子變數類
12個原子變數類,分為4組;
- 標量類:AtomicInteger,AtomiceLong,AtomicBoolean,AtomicReference
- 更新器類
- 陣列類
- 複合變數類
原子變數是一種“更好的volatile”
public class CasNumberRange{
private static class IntPair{
final int lower;
final int upper;
public IntPair(int lower,int upper){
this.lower = lower;
this.upper = upper;
}
...
}
private final AtomicReference<IntPair> values = new AtomicReference<IntPair>(new IntPair(0,0));
public int getLower(){
return values.get().lower;
}
public int getUpper(){
return values.get().upper;
}
public void setLower(int i){
while(true){
IntPair oldv = values.get();
if(i > oldv.upper){
throw new IllegalArgumentException("Can't set lower to "+i+" > upper");
}
IntPair newv = new IntPair(i,oldv.upper);
if(values.compareAndSet(oldv,newv)){
return;
}
}
}
...
}
非阻塞演算法
如果在某種演算法中,一個執行緒的失敗或掛起不會導致其他執行緒也失敗或掛起,那麼這種演算法被稱為非阻塞演算法。如果在演算法的每個步驟中都存在某個執行緒能夠執行下去,那麼這種演算法也被稱為無鎖演算法。
非阻塞的棧
建立非阻塞演算法的關鍵在於,找出如何將原子修改的範圍縮小到單個變數上,同時還要維護資料一致性。
使用Trebier演算法構造的非阻塞棧:
public class ConcurrentStack<E> {
AtomicReference<Node<E>> top = new AtomicReference<Node<E>>();
public void push(E item){
Node<E> newHead = new Node<E>(item);
Node<E> oldHead;
do{
oldHead = top.get();
newHead.next = oldHead;
} while(!top.compareAndSet(oldHead,newHead));
}
public E pop(){
Node<E> oldHead;
Node<E> newHead;
do{
oldHead = top.get();
if(oldHead == null){
return null;
}
newHead = oldHead.next;
} while (!top.compareAndSet(oldHead,newHead));
return oldHead.item;
}
private static class Node<E> {
public final E item;
public Node<E> next;
public Node(E item){
this.item = item;
}
}
}
非阻塞的連結串列
連結需要維護頭指標和尾指標。問題在於如何同時維護這兩個變數,避免一致性遭到破壞。
第一個技巧是,即使在一個包含多個步驟的更新操作中,也要確保資料結構總是處於一致的狀態。第二個技巧是,如果當B到達時發現A正在修改資料結構,那麼資料結構中應該有足夠多的資訊,使得B能完成A的更新操作。
ABA問題
一個值由A變為B,然後在變為A。引入版本號。
相關文章
- 【進階之路】併發程式設計(三)-非阻塞同步機制程式設計
- Java併發程式設計之原子變數Java程式設計變數
- ♻️同步和非同步;並行和併發;阻塞和非阻塞非同步並行
- Socket程式設計中的同步、非同步、阻塞和非阻塞(轉)程式設計非同步
- Java 8 併發: 原子變數和 ConcurrentMapJava變數
- 程式執行緒、同步非同步、阻塞非阻塞、併發並行執行緒非同步並行
- 併發-0-同步/非同步/阻塞/非阻塞/程式/執行緒非同步執行緒
- [譯] 非同步程式設計:阻塞與非阻塞非同步程式設計
- Java併發程式設計實戰--事實不可變物件Java程式設計物件
- Java併發程式設計實戰Java程式設計
- Java併發程式設計:阻塞佇列Java程式設計佇列
- Java併發程式設計——阻塞佇列Java程式設計佇列
- 一文徹底搞定(阻塞/非阻塞/同步/非同步)網路IO、併發程式設計模型、非同步程式設計模型的愛恨情仇非同步程式設計模型
- Java併發程式設計實戰--FutureTaskJava程式設計
- 《JAVA併發程式設計實戰》取消和關閉Java程式設計
- Java併發程式設計實戰--計數訊號量(Semaphore)Java程式設計
- 同步、非同步、阻塞和非阻塞非同步
- Java併發程式設計實戰 05等待-通知機制和活躍性問題Java程式設計
- Java網路程式設計和NIO詳解5:Java 非阻塞 IO 和非同步 IOJava程式設計非同步
- 程式與執行緒、同步與非同步、阻塞與非阻塞、併發與並行執行緒非同步並行
- 阻塞式程式設計和非阻塞式程式設計區別程式設計
- java併發程式設計:同步容器Java程式設計
- 【OS】同步非同步/阻塞非阻塞、併發並行序列的區分非同步並行
- Java併發程式設計實戰--Amdahl定律Java程式設計
- Java 非阻塞 IO 和非同步 IOJava非同步
- java同步非阻塞IOJava
- Java 網路程式設計 —— 非阻塞式程式設計Java程式設計
- 《java併發程式設計的藝術》原子操作類Java程式設計
- Java併發程式設計之鎖機制之AQSJava程式設計AQS
- Java併發系列—併發程式設計挑戰Java程式設計
- Java併發程式設計實戰總結 (一)Java程式設計
- 《JAVA併發程式設計實戰》顯式鎖Java程式設計
- Java併發程式設計實戰(4)- 死鎖Java程式設計
- Java併發程式設計實戰——讀後感Java程式設計
- Java併發程式設計實戰--筆記三Java程式設計筆記
- Java併發程式設計實戰--筆記四Java程式設計筆記
- Java併發程式設計實戰--筆記二Java程式設計筆記
- Java併發程式設計實戰--this引用逸出Java程式設計