概述
ConcurrentLinkedQueue
是一個基於連結節點的無邊界的執行緒安全佇列,它採用先進先出原則對元素進行排序,插入元素放入佇列尾部,出隊時從佇列頭部返回元素,利用CAS方式實現的
ConcurrentLinkedQueue
的結構由頭節點和尾節點組成的,都是使用volatile
修飾的。每個節點由節點元素item
和指向下一個節點的next
引用組成,組成一張連結串列結構。
ConcurrentLinkedQueue
繼承自AbstractQueue
類,實現Queue
介面
常用方法
boolean add(E e)
將指定元素插入此佇列的尾部,當佇列滿時,丟擲異常
boolean contains(Object o)
判斷佇列是否包含次元素
boolean isEmpty()
判斷佇列是否為空
boolean offer(E e)
將元素插入佇列尾部,當佇列滿時返回false
E peek()
獲取佇列頭部元素但不刪除
E poll()
獲取佇列頭部元素,並刪除
boolean remove(Object o)
從佇列中移指定元素
int size()
返回此佇列中的元素數量,需要遍歷一遍集合。判斷佇列是否為空時,不推薦此方法
原始碼分析
private transient volatile Node<E> head;
private transient volatile Node<E> tail;
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;
}
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) {
return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
}
void lazySetNext(Node<E> val) {
UNSAFE.putOrderedObject(this, nextOffset, val);
}
boolean casNext(Node<E> cmp, Node<E> val) {
return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}
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);
}
}
}
複製程式碼
offer入隊操作
初始化
- 初始化操作就是建立一個新結點,並且
head
和tail
相等,結點的資料域為空。
- 當第一次入隊操作時,檢查插入的值是否為空,為空則拋空指標,然後用當前的值建立一個新
Node
結點。然後執行死迴圈開始入隊操作
- 首先定義了兩個指標
p和t
,都指向tail
- 然後定義
q
結點儲存p
的next指向的結點,此時p的next是為空沒有結點的
- 此時
q==null
條件成立。執行p.casNext(null, newNode)
.以cas方式把p的下一個節點指向新建立出來的結點,然後往下執行,p=t
直接返回true。此時初始化構造的結點的next指向第一次入隊成功的結點
- 第二次入隊操作,首先也是非空檢查,然後建立一個新結點。此時死迴圈入隊操作。定義了兩個指標
p和t
,都指向tail
。定義q
結點儲存p
的next指向的結點,此時p的next是不為空的,指向了上面建立的結點。所以q==null
不成立。執行else操作
- 此時
p
也不等於q
。p!=t
不成立,p和t都是指向tail
。因為不成立所以讓p=q
,此時p和q都是指向第二個結點。再次循迴圈操作。
- 然後再次
p和t
,都指向tail
。定義q
結點儲存p
的next指向的結點。此時p的next指向還是空,所以q=null
成立。執行p.casNext(null, newNode)
.以cas方式把p
的next
指向新建立出來的結點。
- 此時
if (p != t)
是成立的 執行casTail(t, newNode);
期望值是t
,更新值新建立的結點。於是更新了tail結點移動到最後新增的結點
大概的入隊流程就是這樣重複上述操作。直到入隊成功。
tail
結點並不是每次都是尾結點。所以每次入隊都要通過
tail
定位尾結點。
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) {
if (p.casNext(null, newNode)) {
if (p != t)
casTail(t, newNode);
return true;
}
}
else if (p == q)
p = (t != (t = tail)) ? t : head;
else
p = (p != t && t != (t = tail)) ? t : q;
}
}
複製程式碼
出隊操作
public E poll() {
restartFromHead:
for (;;) {
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
if (item != null && p.casItem(item, null)) {
if (p != h)
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;
}
}
}
複製程式碼
- 出隊操作是以死迴圈的方式直到出隊成功。 第一次出隊首先執行
for (Node<E> h = head, p = h, q;;)
定義兩個指標p
和h
都指向head
- 然後定義一個item儲存p(這裡就是head)的值,然後判斷item是否為空,此時第一次出隊時為空的,則執行
else if ((q = p.next) == null)
,此條件不成立,因為head的next有結點。執行 else if (p == q)
,此時不相等,因為上個操作已經把q賦值為p的next結點了
。所以執行最後的else語句 p = q;
在次迴圈執行。
- 此時
p.item
不為空條件成立且以cas
方式更新p
的item
為空 p.casItem(item, null)
。如果都兩個條件都成立,判斷 if (p != h)
此時不成立的,更新updateHead(h, ((q = p.next) != null) ? q : p);
預期值是h,
更新值是q
因為不為空。並返回item,第一次出隊成功。
總結
CoucurrentLinkedQueue
的結構由頭節點和尾節點組成的,都是使用volatile
修飾的。每個節點由節點元素item
和指向下一個節點的next
引用組成.
入隊
:先檢查插入的值是否為空,如果是空則丟擲異常。然後以死循壞的方式執行一直到入隊成功,整個過程大概就是把tail
結點的next
指向新結點,然後更新tail
為新結點即可。但是tail
結點並不是每次都是尾結點。所以每次入隊都要通過tail
定位尾結點。
出隊
:出隊操作就是從佇列裡返回一個最早插入的節點元素,並清空該節點對元素的引用。並不是每次出隊都更新head
節點,當head
節點有元素時,直接彈出head
節點的元素,並以cas
方式設定節點的item
為null
,不會更新head
節點。只有當head
節點沒有元素值時,出隊操作才會更新head
節點,這種做法是為了減少cas
方式更新head
節點的消耗,提供出隊的效率