[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 | 安全 | 阻塞 | 重入鎖,效率較低 | 支持 | 有界 |