SynchronousQueue
接著上集繼續,SynchronousQueue是一個不儲存元素的阻塞佇列。每一個put操作必須等待一個take操作,否則不能繼續新增元素,所以其peek()方法始終返回null,沒有資料快取空間。SynchronousQueue支援公平與非公平訪問,預設採用非公平性策略訪問佇列。
構造方法
public SynchronousQueue() {
this(false);
}
public SynchronousQueue(boolean fair) {
transferer = fair ? new TransferQueue() : new TransferStack();
}
複製程式碼
相對於ArrayBlockingQueue利用ReentrantLock實現公平與非公平,而SynchronousQueue利用TransferQueue、TransferStack實現公平與非公平,從命名上來看前者佇列,後者棧,SynchronousQueue的入隊、出隊操作都是基於transfer來實現,ctrl+alt+h檢視方法呼叫
TransferQueue
TransferQueue內部定義如下
// 頭節點
transient volatile QNode head;
// 尾節點
transient volatile QNode tail;
// 指向一個可能還未出隊被取消的節點,因為它在被取消時是最後一個插入節點
transient volatile QNode cleanMe;
// 預設建構函式,建立一個假節點
TransferQueue() {
QNode h = new QNode(null, false); // initialize to dummy node.
head = h;
tail = h;
}
static final class QNode {
// 後繼節點
volatile QNode next;
// item資料
volatile Object item;
// 用來控制阻塞或喚醒
volatile Thread waiter; // to control park/unpark
// 是否是生產者
final boolean isData;
QNode(Object item, boolean isData) {
this.item = item;
this.isData = isData;
}
...
}
...
複製程式碼
公平策略
E transfer(E e, boolean timed, long nanos) {
QNode s = null; // constructed/reused as needed
// 判斷是否是生產者,true為生產者,false為消費者
boolean isData = (e != null);
// 死迴圈
for (;;) {
// 獲取尾節點
QNode t = tail;
// 獲取頭節點
QNode h = head;
// 若尾節點或尾節點為空則跳出本次循序
if (t == null || h == null) // saw uninitialized value
continue; // spin
// 若TransferQueue為空或當前節點與尾節點模式一樣
if (h == t || t.isData == isData) { // empty or same-mode
QNode tn = t.next;
// 若t不是尾節點表明已有其他執行緒操作過,跳出本次迴圈重新來
if (t != tail) // inconsistent read
continue;
// 若之前獲取的尾節點後繼不為空表明已有其他執行緒新增過節點
if (tn != null) { // lagging tail
// CAS將tn置為尾節點
advanceTail(t, tn);
continue;
}
// 若採用了時限模式且超時,直接返回null
if (timed && nanos <= 0) // can't wait
return null;
// 若s為null,構建一個新節點
if (s == null)
s = new QNode(e, isData);
// CAS將新節點加入佇列中,若失敗重新來
if (!t.casNext(null, s)) // failed to link in
continue;
// CAS將新節點s置為尾節點
advanceTail(t, s); // swing tail and wait
// 自旋獲取匹配item
Object x = awaitFulfill(s, e, timed, nanos);
// 若x==s表明執行緒獲取匹配項時,超時或者被中斷,清除節點s
if (x == s) { // wait was cancelled
clean(t, s);
return null;
}
// 判斷節點s是否已經出隊
if (!s.isOffList()) { // not already unlinked
// CAS將節點s置為head,移出佇列
advanceHead(t, s); // unlink if head
if (x != null) // and forget fields
s.item = s;
s.waiter = null;
}
return (x != null) ? (E)x : e;
}
// else分支下述
}
}
複製程式碼
我們假定有執行緒A、B在put操作,執行緒C在take操作,當前TransferQueue初始化如下:
執行緒A新增元素A,head=tail走第一個分支,因為沒有采用鎖機制,所以可能會有其他執行緒搶先操作,其採用各種判斷以及CAS來判斷是否有其他執行緒操作過,新增完尾結點後,會呼叫awaitFulfill方法,其作用是自旋尋找匹配節點,若超過自旋次數此執行緒會阻塞,執行緒被中斷或採用時限模式時獲取超時此次操作會被取消。
Object awaitFulfill(QNode s, E e, boolean timed, long nanos) {
// 獲取最後期限
final long deadline = timed ? System.nanoTime() + nanos : 0L;
// 獲取當前執行緒
Thread w = Thread.currentThread();
// 獲取自旋次數,若新節點s為頭節點後繼節點才能自旋
int spins = ((head.next == s) ?
(timed ? maxTimedSpins : maxUntimedSpins) : 0);
for (;;) {
// 判斷當前執行緒是否被中斷
if (w.isInterrupted())
// 取消當前節點,cas將item置為this
s.tryCancel(e);
// 獲取節點s的item
Object x = s.item;
// 若執行緒中斷,節點s的item與x會不相等,直接返回x
if (x != e)
return x;
// 若採用了時限模式
if (timed) {
// 計算剩餘時間
nanos = deadline - System.nanoTime();
// 若超時,取消節點
if (nanos <= 0L) {
s.tryCancel(e);
continue;
}
}
// 若還有自旋次數,自旋-1
if (spins > 0)
--spins;
// 若等待執行緒為null,將節點s的等待執行緒置為當前執行緒
else if (s.waiter == null)
s.waiter = w;
// 若沒有采用時限模式則呼叫LockSupport.park()直接阻塞執行緒
else if (!timed)
LockSupport.park(this);
// 若剩餘時間超過自旋時間閾值則指定時間阻塞
else if (nanos > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanos);
}
}
void tryCancel(Object cmp) {
UNSAFE.compareAndSwapObject(this, itemOffset, cmp, this);
}
複製程式碼
從原始碼中可以看到只有頭節點後繼才能自旋,執行緒A自旋一段時間匹配節點,若自旋次數用光會一直阻塞,所以每一個執行緒只有匹配到節點後或者因超時、中斷被取消才能繼續新增元素
執行緒A自旋,執行緒B接著put
那麼什麼時候才匹配到呢?在開頭我們提到每一個put操作必須等待一個take操作,這時其他執行緒take(),e為null,isData為false,與尾節點的isData屬性不同,走進else分支,先獲取頭節點的後繼節點資料,若沒有其他執行緒搶先操作,且put操作未被取消,m.casItem(x, e)資料替換,將節點m的item屬性置為null,若CAS替換成功表明匹配成功,在put自旋時會用item與e比較,take()將item置為null,不相等返回null
else { // complementary-mode
// 獲取頭節點後繼
QNode m = h.next; // node to fulfill
// 若t不是尾節點或者m為null或者h不是頭節點,即已有其他執行緒搶先操作過
if (t != tail || m == null || h != head)
continue; // inconsistent read
Object x = m.item;
if (isData == (x != null) || // 節點已被操作過
x == m || // 節點被取消
!m.casItem(x, e)) { // lost CAS
// CAS將m置為頭節點,重來
advanceHead(h, m); // dequeue and retry
continue;
}
// 若走這裡,表明匹配成功
advanceHead(h, m); // successfully fulfilled
// 喚醒m的等待執行緒
LockSupport.unpark(m.waiter);
return (x != null) ? (E)x : e;
}
複製程式碼
TransferStack
TransferStack內部定義如下
// 未執行的消費者
static final int REQUEST = 0;
// 未執行的生產者
static final int DATA = 1;
// 執行緒正在匹配節點
static final int FULFILLING = 2;
volatile SNode head;
static final class SNode {
volatile SNode next; // next node in stack
volatile SNode match; // the node matched to this
volatile Thread waiter; // to control park/unpark
Object item; // data; or null for REQUESTs
int mode;
...
}
...
複製程式碼
TransferStack相對於TransferQueue中的節點,其資料項item與模式mode不需要用volatile修飾,因為它們總是寫在前讀在後。
非公平模式
E transfer(E e, boolean timed, long nanos) {
SNode s = null; // constructed/reused as needed
// REQUEST:消費者;DATA:生產者
int mode = (e == null) ? REQUEST : DATA;
for (;;) {
SNode h = head;
// 若棧為空或者新增元素模式與首元素模式相同
if (h == null || h.mode == mode) { // empty or same-mode
// 超時
if (timed && nanos <= 0) { // can't wait
// 若節點被取消,將取消節點出隊,重新來
if (h != null && h.isCancelled())
casHead(h, h.next); // pop cancelled node
else
return null;
//若不採用限時或者未超時,建立節點CAS將其置為頭節點,s→h
} else if (casHead(h, s = snode(s, e, h, mode))) {
// 自旋匹配
SNode m = awaitFulfill(s, timed, nanos);
// 若m==s表明節點被取消
if (m == s) { // wait was cancelled
clean(s);
return null;
}
if ((h = head) != null && h.next == s)
casHead(h, s.next); // help s's fulfiller
return (E) ((mode == REQUEST) ? m.item : s.item);
}
// 其餘分支下述
}
}
複製程式碼
依然模擬場景,假定現線上程A、B在put,執行緒C、D在take。
執行緒A進行put新增元素A,CAS頭插元素A,呼叫awaitFulfill()自旋匹配,注意只有頭節點、空棧或者協助節點才能自旋,每次自旋都會進行條件判斷,為了
SNode awaitFulfill(SNode s, boolean timed, long nanos) {
// 最後期限
final long deadline = timed ? System.nanoTime() + nanos : 0L;
Thread w = Thread.currentThread();
// 自旋次數
// 若棧為空、節點為首結點或者該節點模式為FULFILLING才能自旋
int spins = (shouldSpin(s) ?
(timed ? maxTimedSpins : maxUntimedSpins) : 0);
for (;;) {
// 若執行緒中斷,取消該節點
if (w.isInterrupted())
s.tryCancel();
// 匹配節點
SNode m = s.match;
if (m != null)
return m;
if (timed) {
nanos = deadline - System.nanoTime();
// 超時,取消節點
if (nanos <= 0L) {
s.tryCancel();
continue;
}
}
// 每次自旋需先判斷是否滿足自旋條件,滿足次數-1
if (spins > 0)
spins = shouldSpin(s) ? (spins-1) : 0;
else if (s.waiter == null)
s.waiter = w; // establish waiter so can park next iter
// 若沒有采用時限模式則呼叫LockSupport.park()直接阻塞執行緒
else if (!timed)
LockSupport.park(this);
// 若剩餘時間超過自旋時間閾值則指定時間阻塞
else if (nanos > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanos);
}
}
複製程式碼
執行緒B接著put元素B,頭節點A的模式與put操作的模式一致,CAS頭插成功後,也呼叫awaitFulfill()自旋,由於頭節點變為執行緒B所以只有執行緒B才能自旋匹配,這也是不公平的體現
節點的取消與公平模式的差不多都是將屬性置為其本身
void tryCancel() {
UNSAFE.compareAndSwapObject(this, matchOffset, null, this);
}
複製程式碼
這時執行緒C進行take操作,take的模式(REQUEST)明顯與當前頭節點B(DATA)不一致且頭節點模式也不為FULFILLING,所以transfer走入else if分支。
// 若頭節點的模式不為 FULFILLING
} else if (!isFulfilling(h.mode)) { // try to fulfill
// 若頭節點被取消,將頭節點出隊重新來
if (h.isCancelled()) // already cancelled
casHead(h, h.next); // pop and retry
else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
for (;;) { // loop until matched or waiters disappear
SNode m = s.next; // m is s's match
if (m == null) { // all waiters are gone
// 將節點s出隊
casHead(s, null); // pop fulfill node
s = null; // use new node next time
break; // restart main loop
}
// 獲取節點m的後繼節點
SNode mn = m.next;
// 嘗試匹配
if (m.tryMatch(s)) {
// 匹配成功,將節點s、m出隊
casHead(s, mn); // pop both s and m
return (E) ((mode == REQUEST) ? m.item : s.item);
} else // lost match
// 若匹配失敗,將m出隊
s.casNext(m, mn); // help unlink
}
}
複製程式碼
建立一個FULFILLING模式的節點並CAS將其置為頭節點,與其後繼匹配,匹配方法如下
boolean tryMatch(SNode s) {
if (match == null &&
UNSAFE.compareAndSwapObject(this, matchOffset, null, s)) {
Thread w = waiter;
if (w != null) { // waiters need at most one unpark
waiter = null;
LockSupport.unpark(w);
}
return true;
}
return match == s;
}
複製程式碼
若節點沒有被取消,其match為null,被取消則為其自身。成功匹配後將一對put、take操作的節點出隊。我們假定另一種場景,若執行緒C的take節點入隊後,未進行匹配執行緒D中途take
頭節點C模式為FULFILLING,transfer走入最後一個分支,並不會先建立節點而是幫助頭節點先行匹配完成入隊出隊操作後,再第二次迴圈繼續執行自己操作
// 頭節點模式為 FULFILLING
} else { // help a fulfiller
SNode m = h.next; // m is h's match
if (m == null) // waiter is gone
casHead(h, null); // pop fulfilling node
else {
SNode mn = m.next;
if (m.tryMatch(h)) // help match
casHead(h, mn); // pop both h and m
else // lost match
h.casNext(m, mn); // help unlink
}
}
複製程式碼
LinkedTransferQueue
LinkedTransferQueue是由連結串列結構組成的無界阻塞FIFO佇列
主要欄位
// 判斷是否多核處理器
private static final boolean MP =
Runtime.getRuntime().availableProcessors() > 1;
// 自旋次數
private static final int FRONT_SPINS = 1 << 7;
// 前驅節點正在操作,當前節點自旋的次數
private static final int CHAINED_SPINS = FRONT_SPINS >>> 1;
static final int SWEEP_THRESHOLD = 32;
// 頭節點
transient volatile Node head;
// 尾節點
private transient volatile Node tail;
// 刪除節點失敗的次數
private transient volatile int sweepVotes;
/**
* xfer()方法中使用
*/
private static final int NOW = 0; // for untimed poll, tryTransfer
private static final int ASYNC = 1; // for offer, put, add
private static final int SYNC = 2; // for transfer, take
private static final int TIMED = 3; // for timed poll, tryTransfer
複製程式碼
Node內部類
static final class Node {
final boolean isData; // false if this is a request node
volatile Object item; // initially non-null if isData; CASed to match
volatile Node next;
volatile Thread waiter;
Node(Object item, boolean isData) {
UNSAFE.putObject(this, itemOffset, item); // relaxed write
this.isData = isData;
}
...
}
複製程式碼
是不是感覺與SynchronousQueue中TransferQueue的QNode節點類定義很類似
xfer
LinkedTransferQueue的大多方法都是基於xfer()方法
/**
* @param e 入隊資料
* @param haveData true:入隊;flase:出隊
* @param how NOW, ASYNC, SYNC, or TIMED
* @param nanos 期限僅TIMED限時模式使用
*/
private E xfer(E e, boolean haveData, int how, long nanos) {
// 若是入隊操作,但無資料拋異常
if (haveData && (e == null))
throw new NullPointerException();
Node s = null; // the node to append, if needed
retry:
for (;;) { // restart on append race
// 從頭節點遍歷
for (Node h = head, p = h; p != null;) { // find & match first node
// 獲取模式isData
boolean isData = p.isData;
// 獲取資料項
Object item = p.item;
// 找到未匹配的節點
if (item != p && (item != null) == isData) { // unmatched
// 若操作模式一樣,不匹配
if (isData == haveData) // can't match
break;
// 若匹配,CAS將替換item
if (p.casItem(item, e)) { // match
for (Node q = p; q != h;) {
Node n = q.next; // update by 2 unless singleton
// 更新 head
if (head == h && casHead(h, n == null ? q : n)) {
h.forgetNext();
break;
} // advance and retry
if ((h = head) == null ||
(q = h.next) == null || !q.isMatched())
break; // unless slack < 2
}
// 喚醒執行緒
LockSupport.unpark(p.waiter);
return LinkedTransferQueue.cast(item);
}
}
// 後繼
Node n = p.next;
// 若p的後繼是其自身,表明p已經有其他執行緒操作過,從頭節點重寫開始
p = (p != n) ? n : (h = head); // Use head if p offlist
}
// 若沒有找到匹配節點,
// NOW為untimed poll, tryTransfer,不會入隊
if (how != NOW) { // No matches available
if (s == null)
// 建立節點
s = new Node(e, haveData);
// 尾插入隊
Node pred = tryAppend(s, haveData);
if (pred == null)
continue retry; // lost race vs opposite mode
// 若不是非同步操作
if (how != ASYNC)
// 阻塞等待匹配值
return awaitMatch(s, pred, e, (how == TIMED), nanos);
}
return e; // not waiting
}
}
複製程式碼
以put()方法為例,假定佇列為空此時有執行緒put(其內部xfer(e, true, ASYNC, 0)),因為不等於now,呼叫tryAppend()方法尾插入隊
private Node tryAppend(Node s, boolean haveData) {
// 從尾節點開始
for (Node t = tail, p = t;;) { // move p to last node and append
Node n, u; // temps for reads of next & tail
// 若佇列為空CAS將S置為頭節點
if (p == null && (p = head) == null) {
if (casHead(null, s))
return s; // initialize
}
else if (p.cannotPrecede(haveData))
return null; // lost race vs opposite mode
// 若不是最後節點
else if ((n = p.next) != null) // not last; keep traversing
p = p != t && t != (u = tail) ? (t = u) : // stale tail
(p != n) ? n : null; // restart if off list
// CAS設定將s置為p的後繼
else if (!p.casNext(null, s))
// 若設定失敗重新來
p = p.next; // re-read on CAS failure
else {
if (p != t) { // update if slack now >= 2
while ((tail != t || !casTail(t, s)) &&
(t = tail) != null &&
(s = t.next) != null && // advance and retry
(s = s.next) != null && s != t);
}
return p;
}
}
}
複製程式碼
從原始碼中可以得知,當第一次tryAppend()佇列為空時只設定了頭節點,第二次tryAppend()才會設定尾結點,入隊後,若不是ASYNC還會呼叫awaitMatch()方法阻塞匹配
private E awaitMatch(Node s, Node pred, E e, boolean timed, long nanos) {
// 若限時獲取最後期限
final long deadline = timed ? System.nanoTime() + nanos : 0L;
Thread w = Thread.currentThread();
int spins = -1; // initialized after first item and cancel checks
ThreadLocalRandom randomYields = null; // bound if needed
for (;;) {
Object item = s.item;
// 不相等表明已經匹配過,有其他執行緒已操作過
if (item != e) { // matched
// assert item != s;
// 取消節點
s.forgetContents(); // avoid garbage
return LinkedTransferQueue.cast(item);
}
// 若執行緒中斷或超時則取消節點
if ((w.isInterrupted() || (timed && nanos <= 0)) &&
s.casItem(e, s)) { // cancel
unsplice(pred, s);
return e;
}
// 初始化自旋次數
if (spins < 0) { // establish spins at/near front
if ((spins = spinsFor(pred, s.isData)) > 0)
randomYields = ThreadLocalRandom.current();
}
// 自旋
else if (spins > 0) { // spin
--spins;
if (randomYields.nextInt(CHAINED_SPINS) == 0)
Thread.yield(); // occasionally yield
}
else if (s.waiter == null) {
s.waiter = w; // request unpark then recheck
}
// 若採用限時則限時阻塞
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos > 0L)
LockSupport.parkNanos(this, nanos);
}
// 直接阻塞
else {
LockSupport.park(this);
}
}
}
複製程式碼
其整個佇列只存在一個操作(入隊或出隊),若不同操作會替換item喚醒相應另個執行緒,若相同操作則根據形參how判斷判斷
NOW:直接返回操作節點不入隊
ASYNC:操作節點尾插入隊,但不會阻塞等待直接返回,同一個執行緒隨即可以接著操作
SYNC:操作節點尾插入隊且會自旋匹配一段時間,自旋次數用完進入阻塞狀態,像SynchronousQueue一樣同一個執行緒操作完必須匹配到或被取消後才能繼續操作
TIMED:限時模式,在指定時間內若沒匹配到操作會被取消
相對於SynchronousQueue,LinkedTransferQueue可以儲存元素且可支援不阻塞形式的操作,而相對於LinkedBlockingQueue維護了入隊鎖和出隊鎖,LinkedTransferQueue通過CAS保證執行緒安全更提高了效率
LinkedBlockingDeque
LinkedBlockingDeque是一個由連結串列結構組成的雙向阻塞佇列,雙向佇列就意味著可以從對頭、對尾兩端插入和移除元素。LinkedBlockingDeque預設構造容量Integer.MAX_VALUE,也可以指定容量
主要屬性
// 頭節點
transient Node first;
// 尾節點
transient Node last;
// 元素個數
private transient int count;
// 容量
private final int capacity;
final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
複製程式碼
Node內部類
static final class Node {
// 資料項
E item;
// 前驅節點
Node prev;
// 後繼節點
Node next;
Node(E x) {
item = x;
}
}
複製程式碼
入隊
public void putFirst(E e) throws InterruptedException {
// 判空
if (e == null) throw new NullPointerException();
// 建立節點
Node node = new Node(e);
final ReentrantLock lock = this.lock;
// 獲取鎖
lock.lock();
try {
while (!linkFirst(node))
notFull.await();
} finally {
lock.unlock();
}
}
複製程式碼
判空處理然後獲取鎖,呼叫linkFirst()入隊
private boolean linkFirst(Node node) {
// assert lock.isHeldByCurrentThread();
// 若當前元素個數超過指定容量,返回false
if (count >= capacity)
return false;
// 獲取首節點
Node f = first;
// 新節點後繼指向首節點
node.next = f;
// 新節點置為首節點
first = node;
// 若佇列為空則新節點置為尾節點
if (last == null)
last = node;
// 若不為空,新節點置為首節點的前驅節點
else
f.prev = node;
// 元素個數+1
++count;
// 喚醒出隊(消費者)等待佇列中執行緒
notEmpty.signal();
return true;
}
複製程式碼
public void putLast(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
Node node = new Node(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
while (!linkLast(node))
notFull.await();
} finally {
lock.unlock();
}
}
複製程式碼
判空處理然後獲取鎖,呼叫linkLast()入隊
private boolean linkLast(Node node) {
// assert lock.isHeldByCurrentThread();
// 若當前元素個數超過指定容量,返回false
if (count >= capacity)
return false;
// 獲取尾節點
Node l = last;
// 將新節點的前驅節點置為原尾節點
node.prev = l;
// 新節點置為尾節點
last = node;
// 若佇列為空,首結點置為頭節點
if (first == null)
first = node;
// 否則將新節點置為原未節點的後繼節點
else
l.next = node;
// 元素個數+1
++count;
// 喚醒出隊(消費者)等待佇列中執行緒
notEmpty.signal();
return true;
}
複製程式碼
出隊
public E takeFirst() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
E x;
while ( (x = unlinkFirst()) == null)
notEmpty.await();
return x;
} finally {
lock.unlock();
}
}
複製程式碼
unlinkFirst()方法
private E unlinkFirst() {
// assert lock.isHeldByCurrentThread();
// 獲取頭節點
Node f = first;
// 若first為null即佇列為空,返回null
if (f == null)
return null;
// 獲取頭節點的後繼節點
Node n = f.next;
E item = f.item;
// 刪除頭節點
f.item = null;
f.next = f; // help GC
// 將原頭節點的後繼節點置為頭節點
first = n;
// 若原佇列僅一個節點,則尾節點置空
if (n == null)
last = null;
// 否則原頭節點的後繼節點的前驅置為null
else
n.prev = null;
// 元素個數-1
--count;
// 喚醒入隊(生產者)等待佇列中執行緒
notFull.signal();
return item;
}
複製程式碼
public E takeLast() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
E x;
while ( (x = unlinkLast()) == null)
notEmpty.await();
return x;
} finally {
lock.unlock();
}
}
複製程式碼
unlinkLast
private E unlinkLast() {
// assert lock.isHeldByCurrentThread();
// 獲取尾節點
Node l = last;
// 尾節點為null即佇列為空,返回null
if (l == null)
return null;
// 獲取原尾節點的前驅節點
Node p = l.prev;
E item = l.item;
// 刪除尾節點
l.item = null;
l.prev = l; // help GC
// 將原尾節點的前驅節點置為尾節點
last = p;
// 若原佇列僅一個節點,則頭節點置空
if (p == null)
first = null;
// 否則原尾節點的前驅節點的後繼置為null
else
p.next = null;
// 元素個數-1
--count;
notFull.signal();
return item;
}
複製程式碼
邏輯就不多說了,看過LinkedList原始碼的應該不會陌生,除了多了喚醒阻塞獲取鎖操作,基本邏輯類似
總結
感謝
《java併發程式設計的藝術》