基於CAS的非阻塞同步佇列的實現(一)
基於CAS實現的元件有很多,這裡選擇非阻塞同步佇列的原因是,當有多個執行緒同時新增元素時,在佇列的尾部如何利用cas保證兩個操作的原子性是有相應演算法的(這兩個步驟是尾節點需要指向新增的元素,最後一個節點元素需要指向新增的元素,即新增的元素必須同時有這兩個節點指向它)。程式碼如下:
public class ConcurentQueue {
static class Node {
public Object e;
public AtomicReference<Node> nextRef ;
public Node(Object e,Node next) {
super();
this.e = e;
this.nextRef = new AtomicReference<Node>(next);
}
}
private Node head = new Node(null,null);
private Node tail = new Node(null,head);
public void add(Object e) {
Node new_node = new Node(e,null);//待插入節點
while(true){
Node curLast = tail.nextRef.get();//當前最後節點
Node next = curLast.nextRef.get();//當前最後節點的next節點
if(next != null){//A 如果當前最後節點的next節點不為空,說明尾節點需要指向next節點
tail.nextRef.compareAndSet(curLast, next);
}else{
if(curLast.nextRef.compareAndSet(null, new_node)){//* B 將當前最後節點的next節點設定為新插入的節點
tail.nextRef.compareAndSet(curLast, new_node);//* C 將尾節點的next節點設定為新插入的節點
return;
}
}
}
}
public Object remove() {
while (true) {
Node next = head.nextRef.get();
if (next == null) {
return null;
} else {
Node nextSetNode = next.nextRef.get();
if (head.nextRef.compareAndSet(next, nextSetNode)) {
return next.e;
}
}
}
}
}
打*的兩個地方原本是需要同時成功才能保證正確性的,但根據現在瞭解的是cas只能保證一個操作的原子性,若要保證兩個操作的原子性,初看是不可能的。細心的朋友可以看出這些程式碼和java併發程式設計實戰中的是幾乎一樣的,也只有將head節點也作為啞節點這一小小改動,(注意尾節點和最後一個節點是不同的節點),首先佇列永遠只有兩種狀態,見書中的原圖(不過描述得改成中間態和穩定態,其餘的不要看尤其是那個”對立“讓人看不懂):
其次佇列永遠只能在最後一個節點後面即穩定態的時候才能插入!
先分析一下程式碼:
1、當B步驟成功後,還沒來得及執行C步驟前,此時佇列就處於中間態,其餘執行緒就會執行A步驟將尾節點推到最後一個節點上從而形成穩定態,此時再執行C步驟時儘管失敗了但是佇列已經穩定;
2、只要佇列不穩定時,所有的執行緒都會設法執行A步驟將佇列趨於穩定;
3、C步驟不管是成功還是失敗佇列都會處於穩定狀態;
到這裡大家可以去體會一下java併發程式設計實戰中說的:那兩個技巧了,說實在的沒看懂程式碼前能看懂那些話麼....,還有能想到這種方法確實不容易,想想還是用鎖實現比較簡單明瞭。