ConcurrentLinkedQueue的入隊和出隊原理詳解

入隊(offer)

/**
 * Inserts the specified element at the tail of this queue.
 * As the queue is unbounded, this method will never return {@code false}.
 *
 * @return {@code true} (as specified by {@link Queue#offer})
 * @throws NullPointerException if the specified element is null
 * 入隊
 *
 * 隊列剛初始化如下:
 *     head
 *  |__null__|
 *     head
 *
 * 入隊A元素:
 * t=p=tail,q=p.next,q==null執行①嘗試入隊
 *     head
 *  |__null__|    null
 *     tail        q
 *      t
 *      p
 *
 * 入隊成功:執行②,p==t,不更新tail,直接退出
 *     head
 *  |__null__| ----> |__A__|
 *     tail    next     q
 *      t
 *      p
 *
 *
 * 再入隊B:
 * t=p=tail,q=p.next=A,q!=null,執行④p!=q,執行⑥:
 * p==t && t==tail 所以三元結果爲q,p=q,第一次循環結束
 *
 *     head
 *  |__null__| ----> |__A__|
 *     tail    next     q
 *      t
 *      p
 *
 * 第一次循環結束,開始第二次循環:q=p.next,q==null,執行①嘗試入隊
 *
 *     head
 *  |__null__| ----> |__A__|
 *     tail    next     p        q
 *      t
 *
 * 入隊成功則執行②:p!=t,需要更新tail,執行③
 *     head                         newNode
 *  |__null__| ----> |__A__| ----> |__B__|
 *     tail    next     p    next     q
 *      t
 *
 * 執行③之後更新tail無論成功與否都返回true
 *     head                         newNode
 *  |__null__| ----> |__A__| ----> |__B__|
 *             next     p    next     q
 *      t                            tail
 *
 *
 * 繼續入隊C:t=p=tail,q=p.next,q==null,執行①嘗試入隊
 *     head
 *  |__null__| ----> |__A__| ----> |__B__|   null
 *             next          next    tail     q
 *                                    t
 *                                    p
 * 入隊成功則執行②:p==t,不需要更新tail,直接返回
 *     head                                        newNode
 *  |__null__| ----> |__A__| ----> |__B__| ---->  |__C__|
 *             next          next    tail   next    q
 *                                    t
 *                                    p
 *
 *
 * 現在考慮一種情況:在併發情況下,該線程正在offer()時,tail因其他線程的poll()被刪了
 * 此時就需要重新定位tail到head位置(tail被刪,說明所有元素都出隊了,隊列爲空)
 *
 * 下圖爲連續出隊ABC之後的結果:
 *
 *     <-------------|                          <-----------|         head
 *  |__null__| ------|    |__null__| -----> |__null__| -----|       |__null__|     null
 *                                    next    tail
 *
 * 下圖爲出隊AB,然後該線程想要入隊D時候的結果:也就是第一次循環時
 *     <-------------|                        head
 *  |__null__| ------|    |__null__| -----> |__null__| -----> |__C__|
 *                                    next    tail      next
 *                                             t                  q
 *                                             p
 * t=p=tail,q=p.next,q!=null,準備執行④,但在執行④之前,其他線程把C出隊了,導致q=p=null,
 * 所以就要執行⑤,復位tail到head的位置。
 *
 * 如果不復位,入隊結點就會插入到tail.next.next位置,新節點更新爲tail後,在tail前面會出現一個
 * null值!
 * 錯誤的結果如下:
 *     <-------------|                        head               ( C )          tail
 *  |__null__| ------|    |__null__| -----> |__null__| -----> |__null__| ----> |__D__|
 *                                    next              next              next
 *                                              t                  p               q
 *
 */
public boolean offer(E e) {
    //非空檢查
    checkNotNull(e);
    //構造結點
    final Node<E> newNode = new Node<E>(e);

    for (Node<E> t = tail, p = t;;) {
        Node<E> q = p.next;
        //定位尾結點,tail不一定是尾結點,可能是tail,也可能是tail.next
        //提升了效率,兩次入隊中,只有一次需要cas操作
        if (q == null) {
            // p is last node
            //q爲null,則p爲尾結點
            if (p.casNext(null, newNode)) {//①
                // Successful CAS is the linearization point
                // for e to become an element of this queue,
                // and for newNode to become "live".
                //首次添加時,p==t,不進行尾結點更新,所以尾結點存在滯後性
                //併發環境,可能存在添加/刪除,tail就更難保證正確指向最後結點
                if (p != t) // hop two nodes at a time②
                    //更新尾結點爲最新元素
                    casTail(t, newNode);  // Failure is OK.③
                    //如果失敗也可以接受,說明有其它線程更新tail成功了
                return true;
            }
            // Lost CAS race to another thread; re-read next
        }
        //p==q==null,則空隊列,說明其他線程此時進行了出隊,將q給刪了
        //如果tail不是尾結點,此時q又是唯一的隊列元素,則tail被刪掉了
        else if (p == q)//④
            // We have fallen off list.  If tail is unchanged, it
            // will also be off-list, in which case we need to
            // jump to head, from which all live nodes are always
            // reachable.  Else the new tail is a better bet.
            //此時需要對tail結點進行復位,復位到head結點
            p = (t != (t = tail)) ? t : head;//⑤
        else
            // Check for tail updates after two hops.
            //推動tail尾結點往隊尾移動
            p = (p != t && t != (t = tail)) ? t : q;//⑥
    }
}

出隊(poll)

/**
 * 如果head結點元素不爲null,則出隊head元素,不更新head結點;
 * 如果head結點元素爲null,則更新head結點
 *
 * 假設已入隊3個元素A、B、C
 * 出隊A:
 * p.item==null,然後執行④,q=A,q!=null,然後執行⑦,
 *     head
 *  |__null__| ------> |__A__| -----> |__B__| -----> |__C__|
 *       h      next            next    tail   next
 *       p                q
 *
 *
 * 執行⑦之後,p=q,進行第二次循環:p.item!=null,出隊;然後執行②p!=h,執行③更新head:
 * q=p.next==B,B!=null,三元結果爲q,移動head=q,同時將原先的頭結點設置爲自引用
 *
 *     head
 *  |__null__| ------> |__A__| -----> |__B__| -----> |__C__|
 *       h      next            next    tail   next
 *                        q
 *                        p
 *
 * 執行③之後:A出隊成功
 *     <-------------|                        head
 *  |__null__| ------|    |__null__| -----> |__B__| -----> |__C__|
 *       h                            next    tail   next
 *                             p               q
 *
 *
 * 出隊B:
 * h=p=head,執行①p.item!=null,直接嘗試出隊;
 *     <-------------|                        head
 *  |__null__| ------|    |__null__| -----> |__B__| -----> |__C__|
 *                                    next    tail   next
 *                                             h
 *                                             p
 * 出隊成功,執行②,p==h,直接返回,結束。
 *     <-------------|                        head
 *  |__null__| ------|    |__null__| -----> |__null__| -----> |__C__|
 *                                    next    tail   next
 *                                             h
 *                                             p
 *
 * 出隊C:
 * h=p=head,執行①,p.item==null;進而執行④,q=p.next,q!=null;然後執行⑦
 *     <-------------|                        head
 *  |__null__| ------|    |__null__| -----> |__null__| -----> |__C__|
 *                                    next    tail      next
 *                                             h                 q
 *                                             p
 *
 * 執行⑦,p=q;進入第二輪循環:執行①,p.item=!=null,嘗試出隊,出隊成功執行②,p!=h,
 * 執行③:q=p.next,q==null,三元結果爲p,更新head到p,將原head設置爲自引用
 *     <-------------|                        head
 *  |__null__| ------|    |__null__| -----> |__null__| -----> |__C__|
 *                                    next    tail      next
 *                                             h                 q
 *                                                               p
 * 執行③之後結果:
 *     <-------------|                          <-----------|         head
 *  |__null__| ------|    |__null__| -----> |__null__| -----|       |__null__|     null
 *                                    next    tail                      p        q
 *                                             h
 *
 *  此時發現一個問題,因爲連續出隊,tail被GC了...如果再入隊,需要重新定位tail=head
 *  纔可以繼續入隊,詳解見入隊的圖示。
 */
public E poll() {
    //標記
    restartFromHead:
    for (;;) {
        for (Node<E> h = head, p = h, q;;) {
            //獲取head元素
            E item = p.item;
            //如果head元素不爲空,則直接cas嘗試出隊
            if (item != null && p.casItem(item, null)) {//①
                // Successful CAS is the linearization point
                // for item to be removed from this queue.
                if (p != h) // hop two nodes at a time ②
                    //更新head,如果p.next爲null,則head爲p,如果p.next!=null,則head爲q(p.next)
                    updateHead(h, ((q = p.next) != null) ? q : p);//③
                //返回元素
                return item;
            }
            //p.next == null則空隊列,更新head,返回null
            else if ((q = p.next) == null) {//④
                updateHead(h, p);//⑤
                return null;
            }
            //第一輪更新失敗(沒有競爭過其他線程),跳回標誌位重新開始
            else if (p == q)//⑥
                continue restartFromHead;
            //移動head結點爲head.next
            else
                p = q;//⑦
        }
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章