入隊(offer)
/**
* Inserts the specified element at the tail of this queue.
* As the queue is unbounded, this method will never return {@code false}.
*
* @return {@code true} (as specified by {@link Queue#offer})
* @throws NullPointerException if the specified element is null
* 入隊
*
* 隊列剛初始化如下:
* head
* |__null__|
* head
*
* 入隊A元素:
* t=p=tail,q=p.next,q==null執行①嘗試入隊
* head
* |__null__| null
* tail q
* t
* p
*
* 入隊成功:執行②,p==t,不更新tail,直接退出
* head
* |__null__| ----> |__A__|
* tail next q
* t
* p
*
*
* 再入隊B:
* t=p=tail,q=p.next=A,q!=null,執行④p!=q,執行⑥:
* p==t && t==tail 所以三元結果爲q,p=q,第一次循環結束
*
* head
* |__null__| ----> |__A__|
* tail next q
* t
* p
*
* 第一次循環結束,開始第二次循環:q=p.next,q==null,執行①嘗試入隊
*
* head
* |__null__| ----> |__A__|
* tail next p q
* t
*
* 入隊成功則執行②:p!=t,需要更新tail,執行③
* head newNode
* |__null__| ----> |__A__| ----> |__B__|
* tail next p next q
* t
*
* 執行③之後更新tail無論成功與否都返回true
* head newNode
* |__null__| ----> |__A__| ----> |__B__|
* next p next q
* t tail
*
*
* 繼續入隊C:t=p=tail,q=p.next,q==null,執行①嘗試入隊
* head
* |__null__| ----> |__A__| ----> |__B__| null
* next next tail q
* t
* p
* 入隊成功則執行②:p==t,不需要更新tail,直接返回
* head newNode
* |__null__| ----> |__A__| ----> |__B__| ----> |__C__|
* next next tail next q
* t
* p
*
*
* 現在考慮一種情況:在併發情況下,該線程正在offer()時,tail因其他線程的poll()被刪了
* 此時就需要重新定位tail到head位置(tail被刪,說明所有元素都出隊了,隊列爲空)
*
* 下圖爲連續出隊ABC之後的結果:
*
* <-------------| <-----------| head
* |__null__| ------| |__null__| -----> |__null__| -----| |__null__| null
* next tail
*
* 下圖爲出隊AB,然後該線程想要入隊D時候的結果:也就是第一次循環時
* <-------------| head
* |__null__| ------| |__null__| -----> |__null__| -----> |__C__|
* next tail next
* t q
* p
* t=p=tail,q=p.next,q!=null,準備執行④,但在執行④之前,其他線程把C出隊了,導致q=p=null,
* 所以就要執行⑤,復位tail到head的位置。
*
* 如果不復位,入隊結點就會插入到tail.next.next位置,新節點更新爲tail後,在tail前面會出現一個
* null值!
* 錯誤的結果如下:
* <-------------| head ( C ) tail
* |__null__| ------| |__null__| -----> |__null__| -----> |__null__| ----> |__D__|
* next next next
* t p q
*
*/
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;
//定位尾結點,tail不一定是尾結點,可能是tail,也可能是tail.next
//提升了效率,兩次入隊中,只有一次需要cas操作
if (q == null) {
// p is last node
//q爲null,則p爲尾結點
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==t,不進行尾結點更新,所以尾結點存在滯後性
//併發環境,可能存在添加/刪除,tail就更難保證正確指向最後結點
if (p != t) // hop two nodes at a time②
//更新尾結點爲最新元素
casTail(t, newNode); // Failure is OK.③
//如果失敗也可以接受,說明有其它線程更新tail成功了
return true;
}
// Lost CAS race to another thread; re-read next
}
//p==q==null,則空隊列,說明其他線程此時進行了出隊,將q給刪了
//如果tail不是尾結點,此時q又是唯一的隊列元素,則tail被刪掉了
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.
//此時需要對tail結點進行復位,復位到head結點
p = (t != (t = tail)) ? t : head;//⑤
else
// Check for tail updates after two hops.
//推動tail尾結點往隊尾移動
p = (p != t && t != (t = tail)) ? t : q;//⑥
}
}
出隊(poll)
/**
* 如果head結點元素不爲null,則出隊head元素,不更新head結點;
* 如果head結點元素爲null,則更新head結點
*
* 假設已入隊3個元素A、B、C
* 出隊A:
* p.item==null,然後執行④,q=A,q!=null,然後執行⑦,
* head
* |__null__| ------> |__A__| -----> |__B__| -----> |__C__|
* h next next tail next
* p q
*
*
* 執行⑦之後,p=q,進行第二次循環:p.item!=null,出隊;然後執行②p!=h,執行③更新head:
* q=p.next==B,B!=null,三元結果爲q,移動head=q,同時將原先的頭結點設置爲自引用
*
* head
* |__null__| ------> |__A__| -----> |__B__| -----> |__C__|
* h next next tail next
* q
* p
*
* 執行③之後:A出隊成功
* <-------------| head
* |__null__| ------| |__null__| -----> |__B__| -----> |__C__|
* h next tail next
* p q
*
*
* 出隊B:
* h=p=head,執行①p.item!=null,直接嘗試出隊;
* <-------------| head
* |__null__| ------| |__null__| -----> |__B__| -----> |__C__|
* next tail next
* h
* p
* 出隊成功,執行②,p==h,直接返回,結束。
* <-------------| head
* |__null__| ------| |__null__| -----> |__null__| -----> |__C__|
* next tail next
* h
* p
*
* 出隊C:
* h=p=head,執行①,p.item==null;進而執行④,q=p.next,q!=null;然後執行⑦
* <-------------| head
* |__null__| ------| |__null__| -----> |__null__| -----> |__C__|
* next tail next
* h q
* p
*
* 執行⑦,p=q;進入第二輪循環:執行①,p.item=!=null,嘗試出隊,出隊成功執行②,p!=h,
* 執行③:q=p.next,q==null,三元結果爲p,更新head到p,將原head設置爲自引用
* <-------------| head
* |__null__| ------| |__null__| -----> |__null__| -----> |__C__|
* next tail next
* h q
* p
* 執行③之後結果:
* <-------------| <-----------| head
* |__null__| ------| |__null__| -----> |__null__| -----| |__null__| null
* next tail p q
* h
*
* 此時發現一個問題,因爲連續出隊,tail被GC了...如果再入隊,需要重新定位tail=head
* 纔可以繼續入隊,詳解見入隊的圖示。
*/
public E poll() {
//標記
restartFromHead:
for (;;) {
for (Node<E> h = head, p = h, q;;) {
//獲取head元素
E item = p.item;
//如果head元素不爲空,則直接cas嘗試出隊
if (item != null && p.casItem(item, null)) {//①
// Successful CAS is the linearization point
// for item to be removed from this queue.
if (p != h) // hop two nodes at a time ②
//更新head,如果p.next爲null,則head爲p,如果p.next!=null,則head爲q(p.next)
updateHead(h, ((q = p.next) != null) ? q : p);//③
//返回元素
return item;
}
//p.next == null則空隊列,更新head,返回null
else if ((q = p.next) == null) {//④
updateHead(h, p);//⑤
return null;
}
//第一輪更新失敗(沒有競爭過其他線程),跳回標誌位重新開始
else if (p == q)//⑥
continue restartFromHead;
//移動head結點爲head.next
else
p = q;//⑦
}
}
}