JAVA多線程之——ConcurrentLinkedQueue

ConcurrentLinkedQueue

ConcurrentLinkedQueue是一個非阻塞的無界隊列。非阻塞和阻塞區別。首先了解一下JAVA中多線程的同步機制基本採用三種方式:

  • volatile 輕量級的線程同步,不會引起上下文的切換和線程調度,提供內存的可見性,但不保證原子性。
  • CAS 輕量級的線程同步,不會引起上下文的切換和線程調度,提供內存的可見性和原子性。
  • 內部鎖(synchronized)和顯示鎖 重量級的線程同步,可能會引起上下文的切換和下城調度,提供內存的可見性和原子性。
    非阻塞算法
    一個線程的失敗和掛起不會引起其他些線程的失敗和掛起,這樣的算法稱爲非阻塞算法。非阻塞算法通過使用底層機器級別的原子指令來取代鎖,從而保證數據在併發訪問下的一致性。

    ConcurrentLinkedQueue就是採用CAS爲基礎來實現非阻塞算法。但是非阻塞算法的設計和實現都非常複雜,這裏只能夠大致學習以及分析一下ConcurrentLinkedQueue的一個實現思路。
    我們都知道鏈表是基於節點實現,所以先學習一下ConcurrentLinkedQueue中的節點:
    Node

private static class Node<E> {
    volatile E item; //節點的域值
    volatile Node<E> next; //當前節點的下一個節點
    Node(E item) {
        UNSAFE.putObject(this, itemOffset, item); //創建新節點。初始化其域值
    }

    boolean casItem(E cmp, E val) {//用 CAS 指令設置 item 域的值
        return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
    }

    void lazySetNext(Node<E> val) {//惰性設置 next 域的值 
        UNSAFE.putOrderedObject(this, nextOffset, val);
    }

    boolean casNext(Node<E> cmp, Node<E> val) {//CAS 設置 next 域的值
        return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
    }

    // Unsafe mechanics

    private static final sun.misc.Unsafe UNSAFE;
    private static final long itemOffset;
    private static final long nextOffset;

    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class k = Node.class;
            itemOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("item"));
            nextOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("next"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
}

瞭解節點後,先來看一下ConcurrentLinkedQueue的一些基本屬性定義

  private transient volatile Node<E> head;
  private transient volatile Node<E> tail;

    public ConcurrentLinkedQueue() {
        head = tail = new Node<E>(null);
    }

隊列有一個head節點,tail節點,在初始化一個隊列的時候,head節點和tail節點都是指向一個域值爲空的節點。

offer方法

public boolean offer(E e) {
    checkNotNull(e);//檢查元素是否爲空。空則拋出NullPointerException
    final Node<E> newNode = new Node<E>(e);//創建一個新節點
    for (Node<E> t = tail, p = t;;) {//死循環。定義兩個節點,一個節點指向tail,一個節點指向t
        Node<E> q = p.next;
        if (q == null) {  //說明p是最後一個節點。
            if (p.casNext(null, newNode)) { //CAS操作把新節點插入到p節點後
                if (p != t)//每插入兩個節點更新一次tail
                    casTail(t, newNode);  // 允許失敗
                return true;
            }
           //CAS失敗,說明有其它線程已經插入,進行下一次循環
        }
        else if (p == q)
             // 遍歷到的p節點已刪除,  
            // 如果實際tail爲當前局部變量tail,說明tail已在head前,需要跳到head進入正常遍歷;  
            // 否則,有其他線程維護過tail,從tail開始  
            p = (t != (t = tail)) ? t : head; 
        else 
            // 每隔兩個節點更新局部變量t,向前推進 
            p = (p != t && t != (t = tail)) ? t : q;
    }
}

上面代碼初次理解可能會比較困難。關鍵在於理解tail的位置,它不一定是指向最後一個節點的。tail有三種情況,第一種指向尾節點。一種指向尾節點的前一個節點。還有一種就是指向了一個已經刪除的節點(p==q)一個節點的下一個節點指向自己,就是說明它已經從鏈表中斷開了。
這三種情況對應着上面的三個判斷情況。
poll

public E poll() {
 restartFromHead:
 for (;;) {
     for (Node<E> h = head, p = h, q;;) {
         E item = p.item;
         if (item != null && p.casItem(item, null)) {
              // CAS null成功,則說明p已從隊列中刪除  
             if (p != h) //// 每刪除兩個節點維護一次head  
                 updateHead(h, ((q = p.next) != null) ? q : p);
             return item;
         }
         else if ((q = p.next) == null) {
             updateHead(h, p);
             return null;
         }
         else if (p == q)// p爲已刪除節點,且已經off-list,重新開始  
             continue restartFromHead;
         else
             p = q;
     }
 }
}
發佈了61 篇原創文章 · 獲贊 23 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章