[java隊列]——ConcurrentLinkedQueue

ConcurrentLinkedQueue介紹

前面介紹過[java隊列]——LinkedBlockingQueue,它是一個可阻塞的線程安全的單鏈表實現的隊列,這裏介紹另一個相似又有區別的隊列ConcurrentLinkedQueue,它有以下特點

  • 非阻塞隊列,不能用於線程池
  • 線程安全,使用CAS+自旋保證
  • 單鏈表實現

ConcurrentLinkedQueue內部實現

基本屬性

public class ConcurrentLinkedDeque<E> extends AbstractCollection<E>implements Deque<E>, java.io.Serializable {
    
    //頭結點
    private transient volatile Node<E> head;

    //尾結點
    private transient volatile Node<E> tail;

	private static class Node<E> {
        volatile E item;
        volatile Node<E> next;
    }

結論:

  • 單鏈表結構

構造方法

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

    public ConcurrentLinkedQueue(Collection<? extends E> c) {
        Node<E> h = null, t = null;
        //遍歷結合,將元素添加到單鏈表中
        for (E e : c) {
            checkNotNull(e);
            Node<E> newNode = new Node<E>(e);
            if (h == null)
                h = t = newNode;
            else {
                t.lazySetNext(newNode);
                t = newNode;
            }
        }
        if (h == null)
            h = t = new Node<E>(null);
        head = h;
        tail = t;
    }

結論:

  • 無參構造,設置頭尾指針,結點值爲null
  • 傳入集合構造,生成初始鏈表

入隊

入隊有兩個方法add(E e)和offer(E e),因爲ConcurrentLinkedQueue是非阻塞隊列,因此沒有put和offer(timeout)方法。

	public boolean add(E e) {
        return offer(e);
    }
    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;
            if (q == null) {
            	//判斷q爲null,則是待插入的尾結點位置
                if (p.casNext(null, newNode)) {
                    //使用cas設置原尾結點的next指向新結點
                    if (p != t) // hop two nodes at a time
                    	//通過cas設置好p->next指向新結點後,如果當前p不是原尾結點t,則說明有其他線程已經調整修改了尾結點
                    	//這時候p並不是原尾結點t,因此要重新這是尾結點t。
                        casTail(t, newNode);  // Failure is OK.
                    return true;
                }
                // Lost CAS race to another thread; re-read next
            }
            else if (p == q)
                //如果p->next = p,說明p已經出隊。因此重新設置p得值
                p = (t != (t = tail)) ? t : head;
            else
                // t後面還有值,重新設置p的值(這裏說明有其他線程入隊了)
                p = (p != t && t != (t = tail)) ? t : q;
        }
    }

結論:

  • 通過tail定位到隊列的尾部,通過CAS把新結點設置到原尾結點的next結點。
  • 如果在設置完之後,更新tail指向新結點。

出隊

與入隊一樣,ConcurrentLinkedQueue出隊一樣只有兩個方法remove和poll

	public E remove() {
        E x = poll();
        if (x != null)
            return x;
        else
            throw new NoSuchElementException();
    }
    
	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取出頭結點,並更新頭結點
                    if (p != h) // hop two nodes at a time
                        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)
                    continue restartFromHead;
                else
                    p = q;
            }
        }
    }

結論:

  • 通過head定位到頭節點,CAS更新其值爲null;
  • 如果CAS更新失敗或者頭節點變化了,就重新尋找頭節點,並重試;
  • 使用CAS和自旋出隊的時候不會阻塞線程,沒找到元素就返回null;

ConcurrentLinkedQueue總結

  • 非阻塞隊列
  • 線程安全,使用CAS+自旋控制隊列操作
  • 不能用於線程池

與LinkedBlockingQueue做比較

隊列 線程安全 阻塞 線程池 有界
ConcurrentLinkedQueue 安全 非阻塞 無鎖,效率較高,使用CAS+自旋 不支持 有界
LinkedBlockingQueue 安全 阻塞 重入鎖,效率較低 支持 有界
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章