ConcurrentLinkedQueue

ConcurrentLinkedQueue閱讀筆記

一、簡介

一種同步的鏈表隊列,採用的自旋+CAS原子更新頭尾節點控制出隊入隊操作

二、繼承關係圖

在這裏插入圖片描述

三、存儲結構

採用鏈表的數據結構,數據更新和取出採用自旋+CAS原子操作

四、源碼分析

內部類

  • Node:也是數據存儲的對象,也是鏈表的節點,包含當前節點值和下一個節點
private static class Node<E> {
    volatile E item;//元素值
    volatile Node<E> next;//下一個節點

    /**
         * Constructs a new node.  Uses relaxed write because item can
         * only be seen after publication via casNext.
         */
    // 構造方法
    Node(E item) {
        // 原子操作初始化item,在指定對象的偏移量寫入一個值
        UNSAFE.putObject(this, itemOffset, item);
    }
	
    // 更新item值
    boolean casItem(E cmp, E val) {
        //CAS原子更新當前節點的item值,cmp代表當前節點item值,val代表期望值
        return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
    }

    // 設置當前節點next的值
    void lazySetNext(Node<E> val) {
        //有序寫入val到當前節點的next中,不保證可見性,但是保證有序性,也就是保證指令不會被重排 
        UNSAFE.putOrderedObject(this, nextOffset, val);
    }

    // CAS原子更新
    boolean casNext(Node<E> cmp, Node<E> val) {
        //CAS原子更新當前節點的next值,cmp是當前節點next值,val是期望值
        return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
    }

    // Unsafe mechanics

    /** 原子操作相關屬性和static初始化 */
    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);
        }
    }
}

屬性

/**	頭結點 */
private transient volatile Node<E> head;
/** 尾節點 */
private transient volatile Node<E> tail;

/** Unsafe原子工具類,head、tail在對象中的偏移量 */
private static final sun.misc.Unsafe UNSAFE;
private static final long headOffset;
private static final long tailOffset;

構造

/** 構造方法一:無參構造 */
public ConcurrentLinkedQueue() {
    // 初始化head、tail節點 都爲`new Node<E>(null)`且相同
    head = tail = new Node<E>(null);
}

/** 構造方法二:使用集合初始化節點 */
public ConcurrentLinkedQueue(Collection<? extends E> c) {
    // h:臨時頭節點
    // t:臨時尾節點
    Node<E> h = null, t = null;
    // 循環遍歷集合c
    // 把集合中元素生成Node對象
    for (E e : c) {
        checkNotNull(e);
        Node<E> newNode = new Node<E>(e);
        if (h == null)
            // 如果h==null,代表第一個節點,直接賦值給h和t
            h = t = newNode;
        else {
            // 如果h!=null,說明已經有頭節點了,
            // 設置t節點next值
            t.lazySetNext(newNode);
            // 把t節點設置爲新的臨時尾節點
            t = newNode;
        }
    } 
    if (h == null)
        // 說明初始化集合參數沒有元素,直接設置臨時頭節點h和臨時尾巴節點t爲`new Node<E>(null)`
        h = t = new Node<E>(null);
    // 把臨時頭節點 h 賦予給head
    // 把臨時尾節點 t 賦予給tail
    head = h;
    tail = t;
}

主要方法

1、入隊操作
  • add(E e):完全等同於offer(E e);
  • offer(E e):添加元素,自旋+CAS進行元素添加,如果添加成功true
/** 添加元素,完全等同於offer(E e) */
public boolean add(E e) {
    // 如果鏈表只要內存足夠是不存在元素空間問題,所以直接調用offer即可
    return offer(e);
}

/** 
添加元素,自旋+CAS進行元素添加,如果添加成功true,
- 定位到鏈表尾節點,嘗試把新節點放到後面
- 如果尾部變化了,則重新拉去尾節點,再重試,直到成功
*/
public boolean offer(E e) {
    // 效驗元素,爲null 拋出異常
    checkNotNull(e);
    // 生成待添加節點
    final Node<E> newNode = new Node<E>(e);

    // 循環:避免其他線程已經更新了尾節點
    // t:緩存 尾節點
    // p:第一次把 t 賦值給p,後續p會持續變化
    // q:p的next節點值,如果爲null 則進入CAS更新
    for (Node<E> t = tail, p = t;;) {
        Node<E> q = p.next;
        if (q == null) {
            // 說明是尾節點的next,直接CAS更新p的next值
            // 如果成功了,就返回true
            // 如果不成功就重新取p.next重新嘗試
            // p is last node
            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 不等於最後一次獲取的尾節點 ,說明有其他線程先一步更新了尾節點
                // 所以,p有做更新,就開始CAS更新tail尾節點
                if (p != t) // hop two nodes at a time
                    casTail(t, newNode);  // Failure is OK.
                // 返回入隊成功
                return true;
            }
            // Lost CAS race to another thread; re-read next
        }
        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.
            // 如果p的next 等於p,說明p節點已經被刪除了
            // 重新設置 t = tail
            // 重新設置 p,
            // 如果原 t 和新獲取tail的 t 不同,說明t有更新,直接用新的t作爲p節點
            // 如果原 t 和新獲取tail的 t 相同,說明沒有節點(可能被出隊了)
            p = (t != (t = tail)) ? t : head;
        else
            // Check for tail updates after two hops.
            // t 後面還有值
            // 重新設置 t = tail
            // 重新設置 p 的值
            p = (p != t && t != (t = tail)) ? t : q;
    }
} 
2、出隊操作
  • 定位到頭節點,嘗試更新其值爲null
  • 如果成功了,就成功出隊
  • 如果失敗了或者頭結點變化了,就重新尋找頭結點,並重試
  • 整個出隊過程沒有一點阻塞相關的代碼,所以出隊的時候不會阻塞線程,沒找到元素就返回null
public E remove() {
    E x = poll();
    if (x != null)
        // 返回刪除後的元素
        return x;
    else
        // 如果隊列沒有元素,則拋出異常
        throw new NoSuchElementException();
}

// 彈出頭元素,成功返回彈出元素,沒有就返回null
public E poll() {
    // 設置標記,用於控制剁成嵌套
    restartFromHead:
    for (;;) {
        // 嘗試彈出頭節點
        for (Node<E> h = head, p = h, q;;) {
            E item = p.item;

            if (item != null && p.casItem(item, null)) {
                // Successful CAS is the linearization point
                // for item to be removed from this queue.
                // 如果 item 不等於null
                // 並且 catItem更新爲null 成功
                // 這個分支只有搶佔到出隊權限的全可以進來
                if (p != h) // hop two nodes at a time
                    // 說明之前有其他線程先取出過一次節點,p有變化了,
                    // 調用updateHead 把head更新爲新q節點
                    // 如果(q = p.next) == null 說明沒有節點了,
                    // 那麼直接更新爲p,也就是沒變化,因爲p等於h
                    updateHead(h, ((q = p.next) != null) ? q : p);
                // 返回出隊的元素值
                return item;
            }
            // 下面三個分支說明頭節點變了
            // 且 p 的 item 肯定爲null
            else if ((q = p.next) == null) {
                // 如果p 的next 爲空,說明隊列中沒有元素了
                // 更新h 爲p,也就是空元素的節點
                updateHead(h, p);
                // 返回null
                return null;
            }
            else if (p == q)
                // 如果p 等於 p 的next,說明 p 已經出隊了,重試
                // 標記之後是loop,纔可以用continue 標記名 
                // 跳過這個循環
                continue restartFromHead;
            else
                // 將 p 設置爲 p 的next (上面q = p.next)
                p = q;
        }
    }
}

/** 獲得隊列頭節點,如果有值返回值,沒有返回null */
public E peek() {
    restartFromHead:
    for (;;) {
        for (Node<E> h = head, p = h, q;;) {
            E item = p.item;
            if (item != null || (q = p.next) == null) {
                // 說明可能item 不等於null,
                // 也可能沒有節點
                updateHead(h, p);
                // 返回獲得的節點,
                return item;
            }
            // 下面2個分支說明頭節點變化了
            // 且p 的 item肯定爲null
            else if (p == q)
                // 說明 p 已經出列了,重試
                continue restartFromHead;
            else
                // 將 p 設置爲 p 的next (上面q = p.next)
                p = q;
        }
    }
}

/** 更新頭結點 */
final void updateHead(Node<E> h, Node<E> p) {
    // 原子更新h爲p成功後,延遲更新h的next爲它自己
    // 這裏用延遲更新是安全的,因爲head節點已經變了
    // 只要入隊出隊的時候檢查head有沒有變化就行了,跟它的next關係不大
    if (h != p && casHead(h, p))
        h.lazySetNext(h);
}
3、移除指定節點
  • 找元素然後去刪除,如果在過程自己(或其他線程已經)刪除了元素,他會幫忙重新關聯節點信息(移除被刪除的節點)
public boolean remove(Object o) {
    // 如果 o 不爲null 則走移除邏輯
    if (o != null) {
        Node<E> next, pred = null;
        for (Node<E> p = first(); p != null; pred = p, p = next) {
            boolean removed = false;//刪除是否成功標記
            E item = p.item;//緩存p.item
            if (item != null) {
                // 說明當前 p 是有值的,節點是存在的
                if (!o.equals(item)) {
                    // 說明次節點不是需要刪除的對象,
                    // 那麼就直接獲取下一個值(此次就是p.next)
                    // succ(E e):獲取下一個節點,如果爲e 和e.next 相同則返回head
                    next = succ(p);
                    // 注意跳過後 會執行:pred = p, p = next
                    continue;
                }
                // 走到這裏說明當前節點的item和需要刪除的值 相同
                // 嘗試更新 刪除當前節點p的item值
                removed = p.casItem(item, null);
            }
            // 走到這裏,只有2個可能,一個是item==null,或者p的item已經cas爲null
            // 所以,item 一定是 null
            // 設置next 爲 p的獲取下一個值(此次就是p.next)
            // succ(E e):獲取下一個節點,如果爲e 和e.next 相同則返回head
            next = succ(p);
            if (pred != null && next != null) // unlink
                // 說明p元素已經被刪除了,或者被其他線程刪除了
                // 直接跳過p  把pred 的next 關聯上next
                // 如:prev -> P -> next  變爲 prev -> next
                pred.casNext(p, next);
            if (removed)
                //如果removed = true,說明已經刪除成功。 
                return true;
        }
    }
    // 循環所有節點沒有找到(或已經被其他線程移除了),移除失敗
    return false;
}

補充

累死了,還補充啥

五、總結

不是阻塞隊列,採用CAS+自旋更新頭尾節點來控制出隊和入隊操作

發佈了48 篇原創文章 · 獲贊 17 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章