Abstract
Treiber Stack Algorithm是一個可擴展的無鎖棧,利用細粒度的併發原語CAS來實現的,Treiber Stack在 R. Kent Treiber在1986年的論文Systems Programming: Coping with Parallelism中首次出現。
基本原理
該算法的基本原理是:只有當您知道要添加的項目是自開始操作以來唯一添加的項目時,纔會添加新的項目。 這是通過使用比較和交換完成的。 在添加新項目時使用堆棧,將堆棧的頂部放在新項目之後。 然後,將這個新構造的頭元素(舊頭)的第二個項目與當前項目進行比較。 如果兩者匹配,那麼你可以將舊頭換成新頭,否則就意味着另一個線程已經向堆棧添加了另一個項目,在這種情況下,你必須再試一次。
當從堆棧中彈出一個項目時,在返回項目之前,您必須檢查另一個線程自操作開始以來沒有添加其他項目。
正確性
在某些語言中,特別是那些沒有垃圾回收的語言,Treiber棧可能面臨ABA問題。當一個進程要從堆棧中移除一個元素時(就在下面的pop例程比較和設置之前),另一個進程可以改變堆棧,使得頭部是相同的,但是第二個元素是不同的。比較和交換將堆棧的頭部設置爲堆棧中舊的第二個元素,混合完整的數據結構。但是,由於Java運行時提供了更強大的保證,所以此頁面上的Java版本不受此問題的影響(新創建的不混淆的對象引用不可能與任何其他可到達的對象引用相同)。
對諸如ABA之類的故障進行測試可能會非常困難,因爲有問題的事件序列非常少見。
Java示例
下面是Java中Treiber Stack的實現,它基於Java Concurrency in Practice提供的
public class ConcurrentStack<E> {
private AtomicReference<Node<E>> top = new AtomicReference<>();
public void push(E item) {
Node<E> newHead = new Node<>(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;
}
}
}
流程分析
PUSH操作
根據上述的描述做圖如上,並分析其工作流程。
- 首先單鏈表保存了各個Stack中的各個元素,成員變量top持有了棧的棧頂元素。
- 當執行push操作時,首先創建一個新的元素爲
newHead
,並讓該新節點的next
指針指向top
節點(此時top=oldHead
)。 - 最後通過CAS替換
top=newHead
,CAS的交換條件是top=oldHead
。 - 當條件滿足後,操作後的狀態如下:
POP操作
根據上述的描述做圖如上,並分析其工作流程。
- 當執行pop操作時,創建一個新的指針,該指針指向
top
的next
元素。 - 然後通過CAS替換
top=newHead
,CAS的交換條件是top=oldHead
。 - 當條件滿足後,操作後的狀態如下: