Java併發容器之LinkedBlockingDeque原始碼分析
一、簡介
LinkedBlockingDeque
來自於JDK1.5
的JUC
包,是一個支援併發操作的有界阻塞佇列,底層資料結構是一個雙鏈表,可以看作LinkedList
的併發版本!
LinkedBlockingDeque
實現了BlockingDeque
介面,而BlockingDeque
繼承了BlockingQueue
並對其進行了擴充套件,這使得LinkedBlockingDeque
不再侷限於FIFO
的傳統佇列元素存取模式,而是可以從佇列的兩端任意進行插入和移除元素,因此更加的靈活。
除了BlockingQueue
的一套方法之外,額外增加了一套以First
結尾的方法比如addFirst
、removeFirst
getFirst
等用於針對佇列頭部的插入、移除和檢查操作,額外增加了一套以Last
結尾的方法比如addLast
、removeLast
、getLast
等用於針對佇列尾部的插入、移除和檢查操作。
和LinkedBlockingQueue
一樣,LinkedBlockingDeque
同樣作為有界佇列,預設元素個數就是最大容量,即Integer.MAX_VALUE
,也可以指定最大容量。
實現了Serializable
介面,支援序列化,沒有實現Cloneable
,不支援克隆!
不支援null
元素!
二、原始碼解析
2.1 主要屬性
由於採用連結串列結構來儲存資料,因此具有頭、尾結點的引用first
、last
Node
型別。由於是一個有界佇列,容量使用capacity
變數來儲存,capacity
是int
型別的,因此LinkedBlockingDeque
的容量最大是Integer.MAX_VALUE
。使用一個int
型別的count
來作為元素計數器。
具有一把ReentrantLock
型別的鎖lock
,生產和消費執行緒都需要獲取同一個鎖。具有兩個條件變數,notEmpty
條件變數用於消費執行緒的阻塞和喚醒,notFull
條件變數用於生產執行緒的阻塞和喚醒!
/** * 頭結點,可以為null */ transient Node<E> first; /** * 尾結點,可以為null */ transient Node<E> last; /** * 佇列元素計數器 */ private transient int count; /** * 佇列的容量,初始化之後就不能變了 */ private final int capacity; /** * 生產、消費都需要獲取的鎖 */ final ReentrantLock lock = new ReentrantLock(); /** * notEmpty條件物件,當佇列為空時用於掛起消費執行緒 */ private final Condition notEmpty = lock.newCondition(); /** * notFull條件物件,當佇列已滿時用於掛起生產執行緒 */ private final Condition notFull = lock.newCondition(); /** * 雙端佇列的結點實現類 */ static final class Node<E> { /** * 值域,如果結點被刪除則item為null */ E item; /** * 結點的前驅 */ Node<E> prev; /** * 結點的後繼 */ Node<E> next; /** * 構造器 * * @param x 元素值 */ Node(E x) { item = x; } }
2.2 構造器
/**
* 建立一個容量為 Integer.MAX_VALUE 的 LinkedBlockingDeque。
*/
public LinkedBlockingDeque() {
//呼叫另一個構造器,引數為Integer.MAX_VALUE
this(Integer.MAX_VALUE);
}
/**
* 建立一個具有指定容量的 LinkedBlockingDeque。
*
* @param capacity 指定容量
* @throws IllegalArgumentException 如果 capacity 小於1,則丟擲IllegalArgumentException。
*/
public LinkedBlockingDeque(int capacity) {
//capacity大小的校驗
if (capacity <= 0) throw new IllegalArgumentException();
//capacity初始化為指定值
this.capacity = capacity;
}
/**
* 建立一個容量是 Integer.MAX_VALUE 的 LinkedBlockingDeque,包含指定集合的全部元素,元素按該集合迭代器的遍歷順序新增。
*
* @param c 指定集合
* @throws NullPointerException 如果指定集合為或任意元素為null
*/
public LinkedBlockingDeque(Collection<? extends E> c) {
//呼叫另一個構造器,初始化容量為Integer.MAX_VALUE
this(Integer.MAX_VALUE);
final ReentrantLock lock = this.lock;
//這裡和LinkedBlockingQueue是一樣的,需要加鎖來保證資料的可見性,因為頭、尾結點沒有使用volatile修飾
//獲取鎖
lock.lock(); // Never contended, but necessary for visibility
try {
//遍歷指定集合
for (E e : c) {
//null校驗
if (e == null)
throw new NullPointerException();
//呼叫linkLast將指定集合的元素新增到佇列尾部
if (!linkLast(new Node<E>(e)))
//如果linkLast返回false,說明集合元素數量達到了最大容量,因此丟擲異常
throw new IllegalStateException("Deque full");
}
} finally {
//釋放鎖
lock.unlock();
}
}
建立一個容量是Integer.MAX_VALUE
的LinkedBlockingDeque
,包含指定集合的全部元素,元素按該集合迭代器的遍歷順序新增。
如果指定集合為或任意元素為null
,則丟擲NullPointerException
。如果指定集合元素數量超過Integer.MAX_VALUE
,那麼丟擲IllegalStateException
。
2.3 入隊操作
2.3.1 入隊尾
put
、offer
、add
、offer
等方法都是在佇列的尾部新增元素。它們將核心實現委託給putLast
、offerLast
實現
public void put(E e) throws InterruptedException {
//內部直接呼叫putLast方法
putLast(e);
}
public boolean offer(E e) {
return offerLast(e);
}
public boolean add(E e) {
addLast(e);
return true;
}
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
return offerLast(e, timeout, unit);
}
2.3.1.1 putLast(e)方法
將指定的元素插入此佇列的尾部,如果該佇列已滿,則執行緒等待。
如果因為獲取不到鎖而在同步佇列中等待的時候被中斷則會繼續等待,即不響應中斷,如果因為佇列滿了在條件佇列中等待的時候在其他執行緒呼叫signal
、signalAll
方法喚醒該執行緒之前就因為中斷而被喚醒了,也會丟擲InterruptedException
。另外,如果指定元素為null
則丟擲NullPointerException
異常。
public void putLast(E e) throws InterruptedException {
//e的校驗
if (e == null) throw new NullPointerException();
//新建Node結點
Node<E> node = new Node<E>(e);
final ReentrantLock lock = this.lock;
//不可中斷的等待獲取生產者鎖,即不響應中斷
lock.lock();
try {
/*
*迴圈呼叫linkLast嘗試將node結點加入隊尾
*/
while (!linkLast(node))
//如果失敗,表示佇列滿了,那麼該執行緒在notFull條件佇列中等待並釋放鎖,被喚醒之後會繼續嘗試獲取鎖、並迴圈判斷
notFull.await();
} finally {
//釋放鎖
lock.unlock();
}
}
大概步驟為:
- 指定元素
e
的null
檢測,通過之後新建Node
結點傳入指定元素e
。 - 不可中斷的等待獲取鎖,即不響應中斷,獲取不到就在該鎖的同步佇列中等待被喚醒,等待時被中斷之後會繼續嘗試獲取鎖;
- 獲取到鎖之後,迴圈呼叫
linkLast
嘗試將node
結點加入隊尾。如果linkLast
返回false
,表示佇列滿了加入失敗,那麼該執行緒在notFull
條件佇列中等待並釋放鎖,被喚醒之後會繼續嘗試獲取鎖、並繼續迴圈呼叫linkLast
。 - 如果
linkLast
返回true
,表示加入成功,那麼迴圈結束。 - 無論過程中發生了什麼,最後的
finally
中解鎖。
2.3.1.2 offerLast(e)方法
將指定的元素插入到此佇列的尾部。在成功時返回true
,如果此佇列已滿,則不阻塞,則立即返回false
。
如果指定元素e
為null
,則丟擲NullPointerException
異常。
如果因為獲取不到鎖而在同步佇列中等待的時候被中斷則會繼續等待,即不響應中斷。這裡的“不會阻塞”是說的獲取鎖之後如果發現此佇列已滿,則立即返回false
,而不會阻塞在條件佇列上!因此如果該鎖被其他執行緒獲取了,當前呼叫offer
方法的執行緒還是會因為獲取不到鎖而被阻塞在lock
的同步佇列中!
相比於putLast
方法,內部僅僅會呼叫一次linkLast
方法,無論成功還是失敗。
public boolean offerLast(E e) {
//e的校驗
if (e == null) throw new NullPointerException();
//新建Node結點
Node<E> node = new Node<E>(e);
final ReentrantLock lock = this.lock;
//不可中斷的等待獲取鎖,即不響應中斷
lock.lock();
try {
//僅僅呼叫一次linkLast方法,返回linkLast的返回值,無論成功還是失敗
return linkLast(node);
} finally {
//釋放鎖
lock.unlock();
}
}
2.3.1.3 offer(e)方法
將指定的元素插入到此佇列的尾部。在成功時返回true
,如果此佇列已滿,則不阻塞,則立即返回false
。
如果指定元素e
為null
,則丟擲NullPointerException
異常。
如果因為獲取不到鎖而在同步佇列中等待的時候被中斷則會繼續等待,即不響應中斷。這裡的“不會阻塞”是說的獲取鎖之後如果發現此佇列已滿,則立即返回false
,而不會阻塞在條件佇列上!因此如果該鎖被其他執行緒獲取了,當前呼叫offer
方法的執行緒還是會因為獲取不到鎖而被阻塞在lock
的同步佇列中!
內部就是呼叫的offerLast(e)
方法。
public boolean offer(E e) {
//內部直接呼叫offerLast方法
return offerLast(e);
}
2.3.1.4 offerLast(e, timeout, unit)方法
將指定的元素插入此佇列的尾部,如果該佇列已滿,則在到達指定的等待時間之前等待可用的空間。如果插入成功,則返回true
;如果在空間可用前超過了指定的等待時間,則返回false
。
如果因為獲取不到鎖而在同步佇列中等待的時候被中斷則丟擲InterruptedException
,即響應中斷,如果因為佇列滿了在條件佇列中等待的時候在其他執行緒呼叫signal
、signalAll
方法喚醒該執行緒之前就因為中斷而被喚醒了,也會丟擲InterruptedException
。另外,如果指定元素e
為null
則丟擲NullPointerException
異常。
相比於putLast
,並不是無限迴圈,而是迴圈指定的時間。
public boolean offerLast(E e, long timeout, TimeUnit unit)
throws InterruptedException {
//e的校驗
if (e == null) throw new NullPointerException();
//新建Node結點
Node<E> node = new Node<E>(e);
//計算超時時間納秒
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
//可中斷的等待獲取鎖,即響應中斷
lock.lockInterruptibly();
try {
/*
*迴圈呼叫linkLast嘗試將node結點加入隊尾
*/
while (!linkLast(node)) {
//如果加入失敗,判斷剩餘超時時間是否小於等於0,即是否超時
if (nanos <= 0)
//如果超時,那麼直接返回false
return false;
//如果沒有超時,該執行緒在notFull條件佇列中等待nanos時間
//被喚醒或者中斷之後,將會返回剩餘的等待時間,隨後繼續迴圈
nanos = notFull.awaitNanos(nanos);
}
//如果加入成功,那麼返回true
return true;
} finally {
//釋放鎖
lock.unlock();
}
}
2.3.2 入隊頭
2.3.2.1 putFirst(e)方法
將指定的元素插入此雙端佇列的頭部,如果該佇列已滿,則執行緒等待。
如果因為獲取不到鎖而在同步佇列中等待的時候被中斷則會繼續等待,即不響應中斷,如果因為佇列滿了在條件佇列中等待的時候在其他執行緒呼叫signal
、signalAll
方法喚醒該執行緒之前就因為中斷而被喚醒了,也會丟擲InterruptedException
。另外,如果指定元素為null
則丟擲NullPointerException
異常。
很簡單,大概步驟為:
- 指定元素
e
的null
檢測,通過之後新建Node
結點傳入指定元素e
。 - 不可中斷的等待獲取鎖,即不響應中斷,獲取不到就在該鎖的同步佇列中等待被喚醒,等待時被中斷之後會繼續嘗試獲取鎖;
- 獲取到鎖之後,迴圈呼叫
linkFirst
嘗試將node
結點加入隊頭。如果linkFirst
返回false
,表示佇列滿了加入失敗,那麼該執行緒在notFull
條件佇列中等待並釋放鎖,被喚醒之後會繼續嘗試獲取鎖、並繼續迴圈呼叫linkFirst
。 - 如果
linkFirst
返回true
,表示加入成功,那麼迴圈結束。 - 無論過程中發生了什麼,最後的
finally
中解鎖。
public void putFirst(E e) throws InterruptedException {
//e的校驗
if (e == null) throw new NullPointerException();
//新建Node結點
Node<E> node = new Node<E>(e);
final ReentrantLock lock = this.lock;
//不可中斷的等待獲取鎖,即不響應中斷
lock.lock();
try {
/*
*迴圈呼叫linkFirst嘗試將node結點加入隊頭
*/
while (!linkFirst(node))
//如果失敗,表示佇列滿了,那麼該執行緒在notFull條件佇列中等待並釋放鎖,被喚醒之後會繼續嘗試獲取鎖、並迴圈判斷
notFull.await();
} finally {
//釋放鎖
lock.unlock();
}
}
2.3.2.1.1 linkFirst連結尾結點
linkFirst
用於將指定node
結點連結到佇列頭部成為新的頭結點,原理很簡單就是在原頭結點first
指向的結點前面新新增一個node
結點,同時建立prev
和next
的引用關係。如果最開始佇列為空,那麼head
和last
都指向該node
結點。
如果佇列滿了,那麼直接返回false
,如果連結成功,那麼將會喚醒一個在notEmpty
等待的消費執行緒,並返回true
。
/**
* 將指定結點連結到佇列頭部成為新的頭結點
*/
private boolean linkFirst(Node<E> node) {
// assert lock.isHeldByCurrentThread();
//如果佇列滿了,那麼直接返回false
if (count >= capacity)
return false;
//佇列未滿
//f變數儲存此時的first隊頭結點,可能為null
Node<E> f = first;
//新結點的前驅設定為f
node.next = f;
//first指向新結點
first = node;
//如果last也為null,說明佇列為空
if (last == null)
//那麼last也指向該結點
last = node;
//否則說明佇列不為空,f也肯定不為null
else
//f的前驅指向新結點
f.prev = node;
//計數器自增1
++count;
//添加了元素結點之後,喚醒在notEmpty等待的消費執行緒
notEmpty.signal();
//返回true
return true;
}
2.3.2.2 offerFirst(e)方法
將指定的元素插入到此佇列的頭部。在成功時返回true
,如果此佇列已滿,則不阻塞,則立即返回false
。
如果指定元素e
為null
,則丟擲NullPointerException
異常。
如果因為獲取不到鎖而在同步佇列中等待的時候被中斷則會繼續等待,即不響應中斷。這裡的“不會阻塞”是說的獲取鎖之後如果發現此佇列已滿,則立即返回false
,而不會阻塞在條件佇列上!因此如果該鎖被其他執行緒獲取了,當前呼叫offer
方法的執行緒還是會因為獲取不到鎖而被阻塞在lock
的同步佇列中!
相比於putFirst
方法,內部僅僅會呼叫一次linkFirst
方法,無論成功還是失敗。
public boolean offerFirst(E e) {
//e的校驗
if (e == null) throw new NullPointerException();
//新建Node結點
Node<E> node = new Node<E>(e);
final ReentrantLock lock = this.lock;
//不可中斷的等待獲取鎖,即不響應中斷
lock.lock();
try {
//僅僅呼叫一次linkFirst方法,返回linkFirst的返回值,無論成功還是失敗
return linkFirst(node);
} finally {
//釋放鎖
lock.unlock();
}
}
2.3.2.3 offerFirst(e, timeout, unit)方法
將指定的元素插入此佇列的頭部,如果該佇列已滿,則在到達指定的等待時間之前等待可用的空間。如果插入成功,則返回true
;如果在空間可用前超過了指定的等待時間,則返回false
。
如果因為獲取不到鎖而在同步佇列中等待的時候被中斷則丟擲InterruptedException
,即響應中斷,如果因為佇列滿了在條件佇列中等待的時候在其他執行緒呼叫signal
、signalAll
方法喚醒該執行緒之前就因為中斷而被喚醒了,也會丟擲InterruptedException
。另外,如果指定元素e
為null
則丟擲NullPointerException
異常。
相比於putFirst
,並不是無限迴圈,而是迴圈指定的時間。
public boolean offerFirst(E e, long timeout, TimeUnit unit)
throws InterruptedException {
//e的校驗
if (e == null) throw new NullPointerException();
//新建Node結點
Node<E> node = new Node<E>(e);
//計算超時時間納秒
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
//可中斷的等待獲取鎖,即響應中斷
lock.lockInterruptibly();
try {
/*
*迴圈呼叫linkFirst嘗試將node結點加入隊頭
*/
while (!linkFirst(node)) {
//如果加入失敗,判斷剩餘超時時間是否小於等於0,即是否超時
if (nanos <= 0)
//如果超時,那麼直接返回false
return false;
//如果沒有超時,該執行緒在notFull條件佇列中等待nanos時間
//被喚醒或者中斷之後,將會返回剩餘的等待時間,隨後繼續迴圈
nanos = notFull.awaitNanos(nanos);
}
//如果加入成功,那麼返回true
return true;
} finally {
//釋放鎖
lock.unlock();
}
}
2.3.2.4 addFirst(e)方法
將指定元素插入此佇列頭部。成功時返回true
,如果當前沒有可用的空間,則丟擲IllegalStateException
,如果e
元素為null
則丟擲NullPointerException
異常。當使用有容量限制的佇列時,通常首選offerFirst
方法。
如果因為獲取不到鎖而在同步佇列中等待的時候被中斷也會繼續等待獲取鎖,即不響應中斷。如果e
元素為null
則丟擲NullPointerException
異常。
內部實際上就是呼叫的offerFirst
方法,並根據offerFirst
方法的返回值判斷是否需要丟擲異常!
public void addFirst(E e) {
//實際上呼叫的offerFirst方法
if (!offerFirst(e))
//如果插入失敗直接丟擲IllegalStateException異常
throw new IllegalStateException("Deque full");
}
2.4 出隊操作
2.4.1 出隊頭
2.4.1.1 takeFirst()方法
獲取並移除此雙端佇列的頭部元素,如果該佇列已空,則執行緒等待。
如果因為獲取不到鎖而在同步佇列中等待的時候被中斷則會繼續等待,即不響應中斷,如果因為佇列滿了在條件佇列中等待的時候在其他執行緒呼叫signal
、signalAll
方法喚醒該執行緒之前就因為中斷而被喚醒了,也會丟擲InterruptedException
。
很簡單,大概步驟為:
- 不可中斷的等待獲取鎖,即不響應中斷,獲取不到就在該鎖的同步佇列中等待被喚醒,等待時被中斷之後會繼續嘗試獲取鎖;
- 獲取到鎖之後,迴圈呼叫
unlinkFirst
嘗試移除隊頭。如果unlinkFirst
返回null
,表示佇列空了加移除失敗,那麼該執行緒在notEmpty
條件佇列中等待並釋放鎖,被喚醒之後會繼續嘗試獲取鎖、並繼續迴圈呼叫unlinkFirst
。 - 如果
unlinkFirst
返回值x
不為null
,表示加入成功,那麼迴圈結束,返回x
。 - 無論過程中發生了什麼,最後的
finally
中解鎖。
public E takeFirst() throws InterruptedException {
final ReentrantLock lock = this.lock;
//不可中斷的等待獲取鎖,即不響應中斷
lock.lock();
try {
E x;
/*
*迴圈呼叫unlinkFirst嘗試將頭結點出隊並返回結點的item值x
*/
while ((x = unlinkFirst()) == null)
//如果x為null,表示佇列空了,那麼該執行緒在notEmpty條件佇列中等待並釋放鎖,被喚醒之後會繼續嘗試獲取鎖、並迴圈判斷
notEmpty.await();
//x不為null,表示出隊成功,結束迴圈,返回x
return x;
} finally {
//釋放鎖
lock.unlock();
}
}
2.4.1.1.1 unlinkFirst移除隊頭
unlinkFirst
用於移除連結串列頭結點並將其後繼作為新的頭結點,原理很簡單就是移除原頭結點first
和其後繼的prev
和next
的引用關係。如果移除之後佇列為空,那麼head
和last
都指向null
。
如果佇列空了,那麼直接返回null
,如果移除成功,那麼將會喚醒一個在notFull
等待的生產執行緒,並返回頭結點的item
值。
另外,這裡被移除的頭結點會將next
的引用指向自己,除了能夠正常的被GC
回收之外,同時用於迭代器辨認是該結點被刪除了而不是到達了佇列末尾,因為迭代器中以後繼為null
表示迭代完畢,在迭代器的succ
方法部分會講到!
/**
* 嘗試將頭結點出隊並返回結點的item值x
*
* @return 不為null 出隊成功;null 佇列已空
*/
private E unlinkFirst() {
// assert lock.isHeldByCurrentThread();
//f變數儲存此時的first隊頭結點,可能為null
Node<E> f = first;
//如果f為null,表示佇列為空,直接返回null
if (f == null)
return null;
//獲取f的後繼結點n
Node<E> n = f.next;
//獲取f結點的item值item
E item = f.item;
//f的item置空
f.item = null;
//f的後繼指向自己,結點出佇列,同時用於迭代器辨認是該結點被刪除了而不是到達了佇列末尾,因為迭代器中以後繼為null表示迭代完畢,在迭代器的succ方法部分會講到
f.next = f; // help GC
//first指向f的後繼n
first = n;
/*如果n為null*/
if (n == null)
//那麼last指向null
last = null;
/*如果n不為null*/
else
//n的前驅置空
n.prev = null;
//計數器自減1
--count;
//出隊成功之後,喚醒在notFull等待的生產執行緒
notFull.signal();
//返回item
return item;
}
2.4.1.2 take()方法
獲取並移除此雙端佇列的頭部元素,如果該佇列已空,則執行緒等待。
如果因為獲取不到鎖而在同步佇列中等待的時候被中斷則會繼續等待,即不響應中斷,如果因為佇列滿了在條件佇列中等待的時候在其他執行緒呼叫signal
、signalAll
方法喚醒該執行緒之前就因為中斷而被喚醒了,也會丟擲InterruptedException
。
內部就是呼叫的takeFirst()
方法。
public E take() throws InterruptedException {
//內部直接呼叫takeFirst方法
return takeFirst();
}
2.4.1.3 pollFirst()方法
獲取並移除此雙端佇列的頭部元素,如果該佇列已空,則返回null
。
如果因為獲取不到鎖而在同步佇列中等待的時候被中斷則會繼續等待,即不響應中斷。這裡的“不會阻塞”是說的獲取鎖之後如果發現此佇列已空,則立即返回 null
,而不會阻塞在條件佇列上!因此如果該鎖被其他執行緒獲取了,當前呼叫offer
方法的執行緒還是會因為獲取不到鎖而被阻塞在lock
的同步佇列中!
相比於takeFirst
方法,內部僅僅會呼叫一次unlinkFirst
方法,無論返回什麼。
public E pollFirst() {
final ReentrantLock lock = this.lock;
//不可中斷的等待獲取鎖,即不響應中斷
lock.lock();
try {
//僅僅呼叫一次unlinkFirst方法,返回unlinkFirst的返回值,無論成功還是失敗
return unlinkFirst();
} finally {
//釋放鎖
lock.unlock();
}
}
2.4.1.4 poll()方法
獲取並移除此雙端佇列的頭部元素,如果該佇列已空,則返回null
。
如果因為獲取不到鎖而在同步佇列中等待的時候被中斷則會繼續等待,即不響應中斷。這裡的“不會阻塞”是說的獲取鎖之後如果發現此佇列已空,則立即返回null
,而不會阻塞在條件佇列上!因此如果該鎖被其他執行緒獲取了,當前呼叫offer
方法的執行緒還是會因為獲取不到鎖而被阻塞在lock
的同步佇列中!
內部就是呼叫的pollFirst()
方法。
public E poll() {
//內部就是呼叫的pollFirst()方法。
return pollFirst();
}
2.4.1.5 pollFirst(timeout, unit)方法
獲取並移除此雙端佇列的頭部元素,如果該佇列已空,則在到達指定的等待時間之前等待佇列非空。如果移除成功,則返回被移除的頭部元素;如果在佇列非空前超過了指定的等待時間,則返回null
。
如果因為獲取不到鎖而在同步佇列中等待的時候被中斷則丟擲InterruptedException
,即響應中斷,如果因為佇列滿了在條件佇列中等待的時候在其他執行緒呼叫signal
、signalAll
方法喚醒該執行緒之前就因為中斷而被喚醒了,也會丟擲InterruptedException
。
相比於takeFirst
方法,並不是無限迴圈,而是迴圈指定的時間。
public E pollFirst(long timeout, TimeUnit unit)
throws InterruptedException {
//計算超時時間納秒
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
//可中斷的等待獲取鎖,即響應中斷
lock.lockInterruptibly();
try {
E x;
/*
*迴圈呼叫unlinkFirst嘗試將頭結點出隊並返回結點的item值x
*/
while ((x = unlinkFirst()) == null) {
//如果出隊失敗,判斷剩餘超時時間是否小於等於0,即是否超時
if (nanos <= 0)
//如果超時,那麼直接返回null
return null;
//如果沒有超時,該執行緒在notEmpty條件佇列中等待nanos時間
//被喚醒或者中斷之後,將會返回剩餘的等待時間,隨後繼續迴圈
nanos = notEmpty.awaitNanos(nanos);
}
//如果出隊成功,那麼返回true
return x;
} finally {
//釋放鎖
lock.unlock();
}
}
2.4.1.6 poll(timeout, unit)方法
獲取並移除此雙端佇列的頭部元素,如果該佇列已空,則在到達指定的等待時間之前等待佇列非空。如果移除成功,則返回被移除的頭部元素;如果在佇列非空前超過了指定的等待時間,則返回null
。
如果因為獲取不到鎖而在同步佇列中等待的時候被中斷則丟擲InterruptedException
,即響應中斷,如果因為佇列滿了在條件佇列中等待的時候在其他執行緒呼叫signal
、signalAll
方法喚醒該執行緒之前就因為中斷而被喚醒了,也會丟擲InterruptedException
。
內部就是呼叫的pollFirst(timeout
, unit)
方法。
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
//內部就是呼叫的pollFirst(timeout, unit)方法。
return pollFirst(timeout, unit);
}
2.4.1.7 removeFirst()方法
獲取並移除此雙端佇列的頭部元素。此方法與pollFirst
唯一的不同在於如果此雙端佇列為空,它將丟擲一個NoSuchElementException
異常。
內部實際上就是呼叫的pollFirst
方法,並根據pollFirst
方法的返回值判斷是否需要丟擲異常!
public E removeFirst() {
//實際上呼叫的pollFirst方法
E x = pollFirst();
//如果返回值為null,那麼丟擲NoSuchElementException
if (x == null) throw new NoSuchElementException();
//返回x
return x;
}
2.4.1.8 remove()方法
獲取並移除此雙端佇列的頭部元素。此方法與pollFirst
唯一的不同在於如果此雙端佇列為空,它將丟擲一個NoSuchElementException
異常。
內部實際上就是呼叫的removeFirst
方法。
public E remove() {
//內部實際上就是呼叫的removeFirst方法。
return removeFirst();
}
2.4.2 出隊尾
2.4.2.1 takeLast()方法
獲取並移除此雙端佇列的尾部元素,如果該佇列已空,則執行緒等待。
如果因為獲取不到鎖而在同步佇列中等待的時候被中斷則會繼續等待,即不響應中斷,如果因為佇列滿了在條件佇列中等待的時候在其他執行緒呼叫signal
、signalAll
方法喚醒該執行緒之前就因為中斷而被喚醒了,也會丟擲InterruptedException
。
很簡單,大概步驟為:
- 不可中斷的等待獲取鎖,即不響應中斷,獲取不到就在該鎖的同步佇列中等待被喚醒,等待時被中斷之後會繼續嘗試獲取鎖;
- 獲取到鎖之後,迴圈呼叫
unlinkLast
嘗試移除隊尾。如果unlinkLast
返回null
,表示佇列空了移除失敗,那麼該執行緒在notEmpty
條件佇列中等待並釋放鎖,被喚醒之後會繼續嘗試獲取鎖、並繼續迴圈呼叫unlinkLast
。 - 如果
unlinkLast
返回值x
不為null
,表示加入成功,那麼迴圈結束,返回x
。 - 無論過程中發生了什麼,最後的
finally
中解鎖。
public E takeLast() throws InterruptedException {
final ReentrantLock lock = this.lock;
//不可中斷的等待獲取鎖,即不響應中斷
lock.lock();
try {
E x;
/*
*迴圈呼叫unlinkLast嘗試將尾結點出隊並返回結點的item值x
*/
while ((x = unlinkLast()) == null)
//如果x為null,表示佇列空了,那麼該執行緒在notEmpty條件佇列中等待並釋放鎖,被喚醒之後會繼續嘗試獲取鎖、並迴圈判斷
notEmpty.await();
//x不為null,表示出隊成功,結束迴圈,返回x
return x;
} finally {
//釋放鎖
lock.unlock();
}
}
2.4.2.2 pollLast()方法
獲取並移除此雙端佇列的尾部元素,如果該佇列已空,則返回null
。
如果因為獲取不到鎖而在同步佇列中等待的時候被中斷則會繼續等待,即不響應中斷。這裡的“不會阻塞”是說的獲取鎖之後如果發現此佇列已空,則立即返回null
,而不會阻塞在條件佇列上!因此如果該鎖被其他執行緒獲取了,當前呼叫offer
方法的執行緒還是會因為獲取不到鎖而被阻塞在lock
的同步佇列中!
相比於takeLast
方法,內部僅僅會呼叫一次unlinkLast
方法,無論返回什麼。
public E pollLast() {
final ReentrantLock lock = this.lock;
//不可中斷的等待獲取鎖,即不響應中斷
lock.lock();
try {
//僅僅呼叫一次unlinkLast方法,返回unlinkLast的返回值,無論成功還是失敗
return unlinkLast();
} finally {
//釋放鎖
lock.unlock();
}
}
2.4.2.3 pollLast(timeout, unit)方法
獲取並移除此雙端佇列的尾部元素,如果該佇列已空,則在到達指定的等待時間之前等待佇列非空。如果移除成功,則返回被移除的尾部元素;如果在佇列非空前超過了指定的等待時間,則返回null
。
如果因為獲取不到鎖而在同步佇列中等待的時候被中斷則丟擲InterruptedException
,即響應中斷,如果因為佇列滿了在條件佇列中等待的時候在其他執行緒呼叫signal
、signalAll
方法喚醒該執行緒之前就因為中斷而被喚醒了,也會丟擲InterruptedException
。
相比於takeLast
方法,並不是無限迴圈,而是迴圈指定的時間。
public E pollLast(long timeout, TimeUnit unit)
throws InterruptedException {
//計算超時時間納秒
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
//可中斷的等待獲取鎖,即響應中斷
lock.lockInterruptibly();
try {
E x;
/*
*迴圈呼叫unlinkLast嘗試將尾結點出隊並返回結點的item值x
*/
while ((x = unlinkLast()) == null) {
//如果出隊失敗,判斷剩餘超時時間是否小於等於0,即是否超時
if (nanos <= 0)
//如果超時,那麼直接返回null
return null;
//如果沒有超時,該執行緒在notEmpty條件佇列中等待nanos時間
//被喚醒或者中斷之後,將會返回剩餘的等待時間,隨後繼續迴圈
nanos = notEmpty.awaitNanos(nanos);
}
//如果出隊成功,那麼返回true
return x;
} finally {
//釋放鎖
lock.unlock();
}
}
2.4.2.4 removeLast()方法
獲取並移除此雙端佇列的尾部元素。此方法與pollLast
唯一的不同在於如果此雙端佇列為空,它將丟擲一個NoSuchElementException
異常。
內部實際上就是呼叫的pollLast
方法,並根據pollLast
方法的返回值判斷是否需要丟擲異常!
public E removeLast() {
//實際上呼叫的pollFirst方法
E x = pollLast();
//如果返回值為null,那麼丟擲NoSuchElementException
if (x == null) throw new NoSuchElementException();
//返回x
return x;
}
2.4.3 remove(o)方法
從此佇列中移除指定元素的單個例項(如果存在)。如果移除成功則返回true
;沒有找到指定元素或者指定元素為null
則返回false
。
從佇列頭開始遍歷佇列,查詢和具有和指定元素o
使用equals
比較返回true
的item
值的元素p
,然後呼叫unlink
移除p
結點!
public boolean remove(Object o) {
//內部呼叫removeFirstOccurrence方法
return removeFirstOccurrence(o);
}
/**
* 移除第一次出現的指定元素
*
* @param o 指定元素
* @return 如果移除成功則返回 true;沒有找到指定元素或者指定元素為null則返回false。
*/
public boolean removeFirstOccurrence(Object o) {
//如果o為null,直接返回null
if (o == null) return false;
final ReentrantLock lock = this.lock;
//不可中斷的等待獲取鎖,即不響應中斷
lock.lock();
try {
/*
* 重頭到尾遍歷整個連結串列,查詢與具有指定元素o相等的item值的結點
*/
for (Node<E> p = first; p != null; p = p.next) {
//使用equals比較
if (o.equals(p.item)) {
//將該結點移除佇列
unlink(p);
//返回true
return true;
}
}
//沒找到,返回false
return false;
} finally {
//釋放鎖
lock.unlock();
}
}
2.4.3.1 unlink移除指定結點
這裡被移除的結點如果是中間結點,會將item
置為null
,並且它的前驅後繼直接關聯,但是它自己的前驅後繼關係並沒有移除,除了表示該結點出佇列之外,同時用於迭代器辨認是該中間結點是否被刪除了,因為可能存在迭代器正在迭代這個中間結點,此時迭代器就可以跳過這個結點,在迭代器的succ
方法部分會講到。
/**
* 移除指定結點x
*/
void unlink(Node<E> x) {
// assert lock.isHeldByCurrentThread();
//獲取x的前驅p
Node<E> p = x.prev;
//獲取x的後繼n
Node<E> n = x.next;
/*如果p為null,那麼相當於移除頭結點,直接呼叫unlinkFirst方法即可*/
if (p == null) {
unlinkFirst();
}
/*否則,如果n為null,那麼相當於移除尾結點,直接呼叫unlinkLast方法即可*/
else if (n == null) {
unlinkLast();
}
/*否則,表示移除中間結點*/
else {
//p的後繼設定為n
p.next = n;
//n的前驅設定為p
n.prev = p;
//x結點的item值置為null
x.item = null;
// Don't mess with x's links. They may still be in use by
// an iterator.
//這裡沒有將x的prev和next引用置空,因為可能存在迭代器正在迭代這個結點,在迭代器的succ方法部分會講到。
//計數器之間一
--count;
//出隊成功之後,喚醒在notFull等待的生產執行緒
notFull.signal();
}
}
2.5 檢查操作
2.5.1 檢查隊頭
2.5.1.1 peekFirst()方法
獲取但不移除此佇列的頭;如果此佇列為空,則返回null
。
public E peekFirst() {
final ReentrantLock lock = this.lock;
//不可中斷的等待獲取消費者鎖,即不響應中斷
lock.lock();
try {
//如果first為null,那麼返回null,否則返回first的item值
return (first == null) ? null : first.item;
} finally {
//釋放鎖
lock.unlock();
}
}
2.5.1.2 peek()方法
獲取但不移除此佇列的頭;如果此佇列為空,則返回null
。
內部直接呼叫peekFirst
方法。
public E peek() {
//內部直接呼叫peekFirst方法
return peekFirst();
}
2.5.1.3 getFirst()方法
獲取但不移除此佇列的頭;此方法與peekFirst
唯一的不同在於:如果此雙端佇列為空,它將丟擲一個NoSuchElementException
異常。
內部實際上就是呼叫的peekFirst
方法,並根據peekFirst
方法的返回值判斷是否需要丟擲異常!
public E getFirst() {
//內部呼叫peekFirst方法獲取返回值x
E x = peekFirst();
//如果x不為null,那麼返回x;否則丟擲NoSuchElementException異常
if (x == null) throw new NoSuchElementException();
return x;
}
2.5.1.4 element()方法
獲取但不移除此佇列的頭;此方法與peek
的不同之處在於:如果此雙端佇列為空,它將丟擲一個NoSuchElementException
異常。
內部實際上就是呼叫的getFirst
方法!
public E element() {
//內部實際上就是呼叫getFirst方法
return getFirst();
}
2.5.2 檢查隊尾
2.5.2.1 peekLast()方法
獲取但不移除此佇列的尾;如果此佇列為空,則返回null
。
public E peekLast() {
final ReentrantLock lock = this.lock;
//不可中斷的等待獲取消費者鎖,即不響應中斷
lock.lock();
try {
//如果last為null,那麼返回null,否則返回last的item值
return (last == null) ? null : last.item;
} finally {
//釋放鎖
lock.unlock();
}
}
2.5.2.2 getLast()方法
獲取但不移除此佇列的尾;此方法與peekLast
唯一的不同在於:如果此雙端佇列為空,它將丟擲一個NoSuchElementException
異常。
內部實際上就是呼叫的peekLast
方法,並根據peekLast
方法的返回值判斷是否需要丟擲異常!
public E getLast() {
//內部呼叫peekLast方法獲取返回值x
E x = peekLast();
//如果x不為null,那麼返回x;否則丟擲NoSuchElementException異常
if (x == null) throw new NoSuchElementException();
return x;
}
三、總結
LinkedBlockingDeque
可以看作LinkedList
集合的執行緒安全的實現,可以在隊頭和隊尾對元素做出隊和入隊操作,而LinkedBlockingQueue
只能在隊尾入佇列,在隊頭出佇列。LinkedBlockingDeque
相比於LinkedBlockingQueue
,可操作的方法和方式更加多樣。
但是我們也看到LinkedBlockingDeque
內部只有一個鎖,出隊、入隊、size
、迭代等操作都需要獲取該鎖。而LinkedBlockingQueue
則有兩把鎖,分別對隊尾的生產者執行緒和隊頭的消費者執行緒應用不同的鎖,因此LinkedBlockingQueue
的併發度比LinkedBlockingDeque
更高,帶來的問題是迭代等需要遍歷整個佇列的操作需要同時獲取兩把鎖。