上一節咱們以計數器做爲例子描述了非阻塞算法,這一節咱們拿一個稍微複雜一點的數據結構棧來說述非阻塞算法的實踐應用。
1.單線程棧java
public class SingleThreadStack implements Stack{ private Node head; public Node pop() { if (head == null) { return null; } Node h = head; head = head.getNext(); h.setNext(null); return h; } @Override public void push(int value) { Node node = new Node(); node.setValue(value); node.setNext(head); head = node; } }
出棧即把棧頭彈出,而且把棧頭設爲下一個節點,若是棧頭爲空,說明棧是空的,直接返回null。入棧時把新入棧節點的next設爲原來的棧頭,而且把新的節點設爲棧頭。這個棧不是線程安全的,拿出棧來講,若是A、B兩個線程同時出棧,棧中只有2個元素,兩次出棧後棧內元素應該都彈出了,可是若是A、B兩個線程同時執行了head = head.getNext();這條代碼(這條代碼本質是先取出next,再賦值)就可能使得A、B兩個線程兩次出棧操做只出彈出並返回了同一個元素。入棧也是相同的道理。node
2.多線程同步棧算法
public class SynchronizedStack implements Stack { private SingleThreadStack delegate = new SingleThreadStack(); @Override public synchronized Node pop() { return delegate.pop(); } @Override public synchronized void push(int value) { delegate.push(value); } }
爲了更好地說明同步的做用,這裏直接把全部棧操做代理給了前文的單線程棧,能夠看到由於出棧和入棧都是同步串行操做,就不會出現前面說起的多線程併發操做致使的兩個線程同時操做只出了一個元素的狀況。實際是把多線程併發操做排了個隊,先拿到鎖的先操做,後拿到鎖的後操做。安全
3.無鎖算法棧數據結構
public class CasNoLockStack implements Stack { private CasNode head; @Override public CasNode pop() { for (; ; ) { CasNode h = head; // 當前棧是空的 if (h == null) { return null; } CasNode next = h; if (head.casSet(h, next)) { h.setNext(null); return h; } } } @Override public void push(int value) { CasNode node = new CasNode(); node.setValue(value); for (; ; ) { CasNode h = head; if (head.casSet(h, node)) { node.setNext(h); return; } } } }
分別從出棧和入棧來講:
出棧時,先在當前線程獲取棧頭賦值給局部變量h,若是h爲空,說明當前棧爲空棧,直接返回null便可。繼續獲取h的下一個節點next,而後嘗試把棧頭用cas(h,next)方法設置爲next節點,這個方法若是返回成功的話,說明已經成功的更新棧頭,那說明h節點是以前最新的棧頭,將h的next節點設置爲空並返回便可。這個方法若是返回失敗的話,說明棧頭已經被其餘線程修改了,那就從新從最開始的第一步開始循環這個過程直到成功爲止,這個循環必定會結束,由於cas操做不成功表明其餘線程已經作了出棧操做,那最後要麼成功在當前線程出棧,要麼其餘線程把棧內元素都彈出,當前線程返回null。
接下來講入棧,入棧時先建立新的棧頭節點node,進入循環後,第一步先獲取當前棧頭賦值給局部變量h,用cas(h,node)嘗試把棧頭賦值給新的入棧節點node,若是成功的話說明其餘線程沒有修改棧頭,當前線程已經修改棧頭成功,再把新棧頭的next指向舊的棧頭便可,若是失敗的話說明其餘線程已經修改了棧頭節點,須要從新循環回到第一步繼續獲取新的棧頭進行操做直到成功爲止。在競爭十分嚴重的狀況下,可能會失敗屢次,執行屢次循環。多線程