ConcurrentLinkedQueue詳解
簡介
實現線程安全的隊列的兩種方式:
- 使用阻塞算法:對入隊和出隊使用同一把鎖(或不同的鎖).
- 使用非阻塞算法:使用循環CAS方式實現.
- ConcurrentLinkedQueue是一個基於鏈接節點的無界線程安全隊列.
- 采用先進先出規則對節點排序.
- 添加元素會添加到隊列的尾部,獲取元素會返回隊列頭部元素.
結構
- 有兩個結點:頭節點(head)和尾節點(tail).
- 每個節點由結點元素(item)和指向下一個節點的指針(next)組成.
- 默認情況下head節點存儲的元素為空,tail節點等於head節點.
操作
入隊列
將入隊節點添加到隊列尾部.
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;
}
}
流程:
- 定位尾節點.
- 使用CAS將入隊節點設置為尾節點的next節點,若不成功,則重試.
定位尾節點:
tail節點並非總是尾節點.每次入隊必須先通過tail節點查找尾節點.
設置入隊節點為尾節點:
設置入隊節點為當前尾節點的next節點.
- 剛開始head和tail節點都指向空節點.
- 添加一個節點后,head和tail仍然指向空節點.
- 再添加節點時,head指向空節點,tail指向尾節點.
- 再次添加節點時,tail保持不變.
- 再添加結點時,tail指向尾節點.
出隊列
出隊列是從隊列中返回一個節點元素.
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;
}
}
}
流程:
- 當head節點有元素時,直接彈出head節點對應的元素,不更新head節點.
- 當head節點沒有元素時,出隊操作更新head節點.
目的:減少使用CAS更新head節點的消耗,提高效率.
- 先獲取頭節點的元素.
- 再判斷頭節點元素是否為空.如果為空,則另外一個線程已進行出隊操作.
- 若不為空,則使用CAS方式將頭節點的引用設為null.成功則直接返回頭節點元素;不成功則其他線程已進行出隊操作更新head節點,需重新獲取頭節點.
參考:
