文章目錄
BlockingQueue
BlockingQueue常常運用於線程池的阻塞隊列中,顧名思義,它能夠作爲一個具有阻塞作用的先進先出隊列。
BlockingQueue 對插入操作、移除操作、獲取元素操作提供了四種不同的方法用於不同的場景中使用:1、拋出異常;2、返回特殊值(null 或 true/false,取決於具體的操作);3、阻塞等待此操作,直到這個操作成功;4、阻塞等待此操作,直到成功或者超時指定時間。總結如下:
throw exception | special value | blocks | times out | |
---|---|---|---|---|
insert | add(e) | offer(e) | put(e) |
offer(e,time,unit) |
delete | remove() | poll() | take() |
poll(time,unit) |
examine | element() | peek() | not applicable | not applicable |
實現1:ArrayBlockingQueue
ArrayBlockingQueue:基於數組結構的有界阻塞隊列,併發控制通過ReentrantLock獲得鎖來實現。
併發同步原理:讀操作和寫操作都需要獲取到 AQS 獨佔鎖才能進行操作【對比之下LinkedBlockingQueue有兩個鎖,分別爲讀取鎖和寫入鎖,能同時讀和寫】。如果隊列爲空,這個時候讀操作的線程進入到讀線程隊列排隊,等待寫線程寫入新的元素,然後喚醒讀線程隊列的第一個等待線程。如果隊列已滿,這個時候寫操作的線程進入到寫線程隊列排隊,等待讀線程將隊列元素移除騰出空間,然後喚醒寫線程隊列的第一個等待線程。
屬性及構造函數
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
/**
* 存放元素的數組
*/
final Object[] items;
/**
* 下一次讀取操作的位置
*/
int takeIndex;
/**
* 下一次存放操作的位置
*/
int putIndex;
/**
* 隊列中對象數量
*/
int count;
//下面三個爲控制併發用到的同步器
final ReentrantLock lock;
private final Condition notEmpty;
private final Condition notFull;
//設定隊列容量的構造函數,默認非公平鎖
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
//設定隊列容量和鎖公平性的構造函數
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
//指定集合的構造函數,將此集合中的元素在構造方法期間就先添加到隊列中。
public ArrayBlockingQueue(int capacity, boolean fair,
Collection<? extends E> c) {
this(capacity, fair);
final ReentrantLock lock = this.lock;
lock.lock(); // Lock only for visibility, not mutual exclusion
try {
int i = 0;
try {
for (E e : c) {
checkNotNull(e);
items[i++] = e;
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
count = i;
putIndex = (i == capacity) ? 0 : i;
} finally {
lock.unlock();
}
}
}
put
public void put(E e) throws InterruptedException {
//元素不能爲null
checkNotNull(e);
final ReentrantLock lock = this.lock;
//可中斷鎖
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
take
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
實現2:LinkedBlockingQueue
LinkedBlockingQueue:基於鏈表結構的“無界”阻塞隊列,併發控制同樣通過ReentrantLock獲得鎖來實現,不過LinkedBlockingQueue採用了雙鎖
:讀取鎖和寫入鎖,相比ArrayBlockingQueue能夠同時進行讀和寫,吞吐量更高!
線程池Executors.newFixedThreadPool()採用了此隊列。
併發同步原理
使用了兩個鎖,兩個Condition
takeLock和notEmpty 搭配:如果獲取(take)一個元素,首先要獲得鎖(takeLock),這還不夠,還要繼續判斷隊列是否爲空(notEmpty)這個條件(Condition)
putLock和notFull 搭配:如果插入(put)一個元素,首先要獲得鎖(putLock),這還不夠,還要繼續判斷隊列是否已滿(notFull)這個條件(Condition)
屬性及構造函數
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
//很簡單的結點結構,包含了該結點的元素和後繼結點
static class Node<E> {
E item;
Node<E> next;
Node(E x) { item = x; }
}
//隊列容量
private final int capacity;
//隊列中元素數量
private final AtomicInteger count = new AtomicInteger();
//隊頭結點
transient Node<E> head;
//隊頭=尾結點
private transient Node<E> last;
//元素讀取操作時需要獲得的鎖
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
//元素存入操作時需要獲得的鎖
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();
//因爲默認隊列長度大小爲有符號整數的最大值,所以我們稱其爲“無界隊列”
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
//可設定初始隊列容量的構造函數,這樣它就有界了
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
//初始化空的頭結點
last = head = new Node<E>(null);
}
//利用集合初始化的構造函數
public LinkedBlockingQueue(Collection<? extends E> c) {
this(Integer.MAX_VALUE);
final ReentrantLock putLock = this.putLock;
putLock.lock(); // Never contended, but necessary for visibility
try {
int n = 0;
for (E e : c) {
if (e == null)
throw new NullPointerException();
if (n == capacity)
throw new IllegalStateException("Queue full");
enqueue(new Node<E>(e));
++n;
}
count.set(n);
} finally {
putLock.unlock();
}
}
put
public void put(E e) throws InterruptedException {
//元素不能爲null
if (e == null) throw new NullPointerException();
// Note: convention in all put/take/etc is to preset local var
// holding count negative to indicate failure unless set.
//c初始化爲-1 ,用於標識插入操作是否成功,offer方法返回布爾值就是通過c是否>=0來判斷的
int c = -1;
Node<E> node = new Node<E>(e);
//加鎖
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
//隊列滿了則阻塞
while (count.get() == capacity) {
notFull.await();
}
//新的元素結點插到隊列尾部
enqueue(node);
c = count.getAndIncrement();
//c + 1 < capacity說明隊列未滿,調用 notFull.signal() 喚醒等待線程
if (c + 1 < capacity)
notFull.signal();
} finally {
//入隊後釋放鎖
putLock.unlock();
}
//c==0說明之前隊列是空的,這時插入一個元素後隊列不爲空,因此喚醒notEmpty這個條件
if (c == 0)
signalNotEmpty();
}
//入隊很簡單,將新結點插入到隊列末尾,last指針更新
private void enqueue(Node<E> node) {
// assert putLock.isHeldByCurrentThread();
// assert last.next == null;
last = last.next = node;
}
take
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
notEmpty.await();
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
//出隊
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
//頭結點一直保持爲null,每次從隊列中取元素實際上是取頭結點後面一個節點的值
Node<E> h = head;
Node<E> first = h.next;
h.next = h; // help GC
head = first;
E x = first.item;
first.item = null;
return x;
}
實現3:SynchronousQueue
SynchronousQueue:不存儲元素的阻塞隊列,即隊列是虛的,不存儲元素,吞吐量通常高於LinkedBlockingQueue
,線程池Executors.newCacheThreadPool()採用了此隊列。
併發同步原理
當一個線程往隊列中寫元素時,寫入操作不會立即返回,需要等待另一個線程來將這個元素拿走;同理當一個讀線程做讀操作的時候,同樣需要一個相匹配的寫線程的寫操作。
這裏的Synchronous指的就是寫線程要和讀線程同步,一個寫線程匹配一個讀線程。
SynchronousQueue 的隊列其實是虛的,其不提供任何空間(一個都沒有)來存儲元素。數據必須從某個寫線程交給某個讀線程,而不是寫到某個隊列中等待被消費。
屬性及構造函數
SynchronousQueue內部有一個抽象類Transferer,它的兩個實現類TransferQueue和TransferStack分別用於公平和非公平模式下。
public class SynchronousQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
//默認非公平模式
public SynchronousQueue() {
this(false);
}
//設定公平模式的構造函數,公平用TransferQueue,非公平用TransferStack
public SynchronousQueue(boolean fair) {
transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}
//抽象類Transferer中只有一個抽象方法,讓我們來看一下:
abstract static class Transferer<E> {
/**
* Performs a put or take.
*
* @param e 如果非null,表示將元素從生產者轉移到消費者
* 如果爲null,表示消費者等待生產者提供元素,返回值爲生產者提供的元素
* @param timed 是否設置超時
* @param 超時時間
* @return 非空,則表示轉移的元素;
* 空,則表示超時或者中斷。
* 調用者可以通過線程的中斷狀態判斷具體是哪種情況導致的
*/
abstract E transfer(E e, boolean timed, long nanos);
}
}
我們先採用公平模式分析源碼,然後再說說公平模式和非公平模式的區別。
put和take
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
//根據transfer的返回值是否爲null來判斷是否轉移元素成功
if (transferer.transfer(e, false, 0) == null) {
//未成功,線程中斷,拋出異常
Thread.interrupted();
throw new InterruptedException();
}
}
public E take() throws InterruptedException {
//和put的區別在於第一個參數爲null,表示取元素
E e = transferer.transfer(null, false, 0);
//根據transfer的返回值是否爲null來判斷是否轉移元素成功
if (e != null)
return e;
Thread.interrupted();
throw new InterruptedException();
}
由代碼可以看出take和put操作都是通過transfer實現的,它們對此方法實現最大的區別在於方法的第一個參數,put不爲null,take爲null,可以說SynchronousQueue的核心功能就是通過transfer的實現的,下面來看transfer方法。
transfer
公平模式
transfer的設計思路:
- 調用此方法時,若隊列爲空或者隊列中的結點和當前線程的操作類型一致(即當前操作爲put操作,隊列中的結點的線程屬性都爲寫線程;take操作和讀線程同理),則將當前線程加入到等待隊列中
2.如果隊列中有等待結點,而且與當前操作匹配(即當前操作位put操作,隊列中結點線程線程屬性都爲讀線程,這就構成了匹配;take操作和寫線程同理)。這種情況下,匹配等待隊列的隊頭,出隊,返回響應數據。
下面來看看官方註釋:
/* Basic algorithm is to loop trying to take either of
* two actions:
*
* 1. If queue apparently empty or holding same-mode nodes,
* try to add node to queue of waiters, wait to be
* fulfilled (or cancelled) and return matching item.
*
* 2. If queue apparently contains waiting items, and this
* call is of complementary mode, try to fulfill by CAS'ing
* item field of waiting node and dequeuing it, and then
* returning matching item.
*
* In each case, along the way, check for and try to help
* advance head and tail on behalf of other stalled/slow
* threads.
*
* The loop starts off with a null check guarding against
* seeing uninitialized head or tail values. This never
* happens in current SynchronousQueue, but could if
* callers held non-volatile/final ref to the
* transferer. The check is here anyway because it places
* null checks at top of loop, which is usually faster
* than having them implicitly interspersed.
*/
對於上面提到的隊列結點,它的結構是這樣的:
/**
* 等待隊列的結點類
*/
static final class QNode {
volatile QNode next; // 只有有個後繼結點指針,說明是單向鏈表
volatile Object item; // CAS'ed to or from null
volatile Thread waiter; // 保存線程對象,用於掛起和喚醒
final boolean isData; //判斷是寫線程結點還是讀線程結點 boolean isData = (e != null);
}
下面正式分析transfer源碼:
//==================TransferQueue的transfer方法===============================
@SuppressWarnings("unchecked")
E transfer(E e, boolean timed, long nanos) {
QNode s = null; // constructed/reused as needed
boolean isData = (e != null);
for (; ; ) {
QNode t = tail;
QNode h = head;
//頭結點和尾結點爲空,說明還沒有初始化,continue即可。
if (t == null || h == null) // saw uninitialized value
continue; // spin
//隊列已初始化,只有一個空結點或者當前結點與隊列結點類型一致
if (h == t || t.isData == isData) { // empty or same-mode
QNode tn = t.next;
//說明剛纔有結點入隊,繼續continue即可
if (t != tail) // inconsistent read
continue;
if (tn != null) { // lagging tail
//尾結點的後繼結點不爲空,說明不是真正的尾結點,CAS將後繼結點設爲尾結點,繼續循環判斷
advanceTail(t, tn);
continue;
}
//用於超時設置
if (timed && nanos <= 0) // can't wait
return null;
if (s == null)
s = new QNode(e, isData);
if (!t.casNext(null, s)) // failed to link in
continue;
//將新結點s設爲新的尾結點
advanceTail(t, s); // swing tail and wait
Object x = awaitFulfill(s, e, timed, nanos);
//當這裏,說明之前的線程被喚醒了
//x==s,說明線程等待超時或者被中斷,就要取消等待
if (x == s) { // wait was cancelled
clean(t, s);
return null;
}
if (!s.isOffList()) { // not already unlinked
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 分支就是上面說的第二種情況,有相應的讀或寫相匹配的情況
else { // complementary-mode
QNode m = h.next; // node to fulfill
if (t != tail || m == null || h != head)
continue; // inconsistent read
Object x = m.item;
if (isData == (x != null) || // m already fulfilled
x == m || // m cancelled
!m.casItem(x, e)) { // lost CAS
advanceHead(h, m); // dequeue and retry
continue;
}
advanceHead(h, m); // successfully fulfilled
LockSupport.unpark(m.waiter);
return (x != null) ? (E) x : e;
}
}
Object awaitFulfill(QNode s, E e, boolean timed, long nanos) {
/* Same idea as TransferStack.awaitFulfill */
final long deadline = timed ? System.nanoTime() + nanos : 0L;
Thread w = Thread.currentThread();
//判斷需要自旋的次數
int spins = ((head.next == s) ?
(timed ? maxTimedSpins : maxUntimedSpins) : 0);
//循環
for (; ; ) {
//如果被中斷了,則取消該結點,就是將s中的item屬性設爲this
if (w.isInterrupted())
s.tryCancel(e);
Object x = s.item;
//方法的唯一出口,調用tryCancel後(超時或者中斷),x!=e
if (x != e)
return x;
//如果需要,檢查是否超時
if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
s.tryCancel(e);
continue;
}
}
if (spins > 0)
--spins;
//自旋次數已到,先檢查s的線程是否爲空,爲空則設置爲當前線程
else if (s.waiter == null)
s.waiter = w;
//自旋次數已到,並且s也封裝了當前的線程,則掛起
else if (!timed)
LockSupport.park(this);
//自旋次數已到,當有設置超時時, spinForTimeoutThreshold 這個之前講 AQS 的時候其實也說過,剩餘時間小於這個閾值的時候,就
// 不要進行掛起了,自旋的性能會比較好
else if (nanos > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanos);
}
}
}
非公平模式
通過分析TransferQueue的transfer源碼大概瞭解了公平模式 下的put和get操作,對於非公平模式,也就是TransferStack的transfer源碼實現,直接敘述不加以分析了(參考:https://www.javadoop.com/post/java-concurrent-queue)
- 當調用這個方法時,如果隊列是空的,或者隊列中的節點和當前的線程操作類型一致(如當前操作是 put 操作,而棧中的元素也都是寫線程)。這種情況下,將當前線程加入到等待棧中,等待配對。然後返回相應的元素,或者如果被取消了的話,返回 null。
- 如果棧中有等待節點,而且與當前操作可以匹配(如棧裏面都是讀操作線程,當前線程是寫操作線程,反之亦然)。將當前節點壓入棧頂,和棧中的節點進行匹配,然後將這兩個節點出棧。配對和出棧的動作其實也不是必須的,因爲下面的一條會執行同樣的事情。
- 如果棧頂是進行匹配而入棧的節點,幫助其進行匹配並出棧,然後再繼續操作。
應該說,TransferStack 的源碼要比 TransferQueue 的複雜一些,如果讀者感興趣,請自行進行源碼閱讀。
兩種模式的對比總結
公平模式
- 採用FIFO隊列思想,隊尾匹配隊頭出隊,先進先出,體現了公平原則;
- 新來的線程若匹配成功,不需要入隊,直接喚醒隊頭線程(注意:head結點是空節點,這裏是指head結點的後繼結點)
非公平模式:
- 採用棧思想,棧頂元素先匹配,先入棧的線程結點後匹配,體現了非公平原則
- 新來的線程若匹配成功,則需要壓棧,然後新線程循環執行匹配線程邏輯,一旦發現沒有併發衝突則成對出棧
這篇文章 https://zhuanlan.zhihu.com/p/29227508 用圖來表示兩種模式的put和take過程,清晰易懂
實現4:PriorityBlockingQueue
PriorityBlockingQueue:一個具有優先級的無界阻塞隊列。優先級是因爲採用堆思想,可以根據元素自身排序,也可以指定比較器進行排序;無界是因爲隊列能夠自動擴容。
併發同步原理
當一個線程往隊列中寫元素或者讀取元素時,會獲取獨佔鎖(ReentrantLock),和ArrayBlocking的同步實現原理很像,區別在於PriorityBlockingQueue中沒有notFull這個條件(Condition)
屬性及構造函數
public class PriorityBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
//默認初始隊列容量
private static final int DEFAULT_INITIAL_CAPACITY = 11;
//隊列最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//隊列採用數組存儲元素,思想是堆
private transient Object[] queue;
//隊列中元素個數
private transient int size;
//比較器
private transient Comparator<? super E> comparator;
//獨佔鎖
private final ReentrantLock lock;
//條件Condition
private final Condition notEmpty;
//擴容時採用的鎖,通過CAS實現
private transient volatile int allocationSpinLock;
//默認構造器,初始化隊列大小爲11
public PriorityBlockingQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
//指定隊列大小的構造器
public PriorityBlockingQueue(int initialCapacity) {
this(initialCapacity, null);
}
//指定隊列大小和比較器的隊列
public PriorityBlockingQueue(int initialCapacity,
Comparator<? super E> comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.lock = new ReentrantLock();
this.notEmpty = lock.newCondition();
this.comparator = comparator;
this.queue = new Object[initialCapacity];
}
}
put
public void put(E e) {
offer(e); // never need to block
}
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
final ReentrantLock lock = this.lock;
//1 獲取獨佔鎖
lock.lock();
int n, cap;
Object[] array;
//2. 插入元素前判斷是否需要擴容
while ((n = size) >= (cap = (array = queue).length))
tryGrow(array, cap);
//3. 插入元素並調整堆結構
try {
Comparator<? super E> cmp = comparator;
//構造器爲空,使用插入元素自帶比較器進行調整
if (cmp == null)
siftUpComparable(n, e, array);
//否則使用指定的構造器進行調整
else
siftUpUsingComparator(n, e, array, cmp);
size = n + 1;
//4. 通知notEmpty條件
notEmpty.signal();
} finally {
//5. 釋放鎖
lock.unlock();
}
return true;
}
由於在插入元素前會判斷是夠需要擴容,下面來講一下如何擴容
tryGrow擴容
/**
* 擴容過程中釋放了獨佔鎖,通過CAS 操作維護 allocationSpinLock狀態,實現專門的擴容鎖, 這樣就可以保證擴容操作和讀操作同時進行
* @param array
* @param oldCap
*/
private void tryGrow(Object[] array, int oldCap) {
//釋放獨佔鎖
lock.unlock(); // must release and then re-acquire main lock
Object[] newArray = null;
// 用 CAS 操作將 allocationSpinLock 由 0 變爲 1,算是獲取擴容鎖
if (allocationSpinLock == 0 &&
UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
0, 1)) {
try {
int newCap = oldCap + ((oldCap < 64) ?
(oldCap + 2) : // grow faster if small
(oldCap >> 1));
if (newCap - MAX_ARRAY_SIZE > 0) { // possible overflow
int minCap = oldCap + 1;
if (minCap < 0 || minCap > MAX_ARRAY_SIZE)
throw new OutOfMemoryError();
newCap = MAX_ARRAY_SIZE;
}
//queue如果不等於array,說明有其他線程給queue分配了空間,newArray爲null
if (newCap > oldCap && queue == array)
newArray = new Object[newCap];
} finally {
//釋放擴容鎖
allocationSpinLock = 0;
}
}
//等待其他線程操作完畢
if (newArray == null) // back off if another thread is allocating
Thread.yield();
//重新獲得獨佔鎖
lock.lock();
// 將原來數組中的元素複製到新分配的大數組中
if (newArray != null && queue == array) {
queue = newArray;
//將舊隊列中元素複製到新隊列中
System.arraycopy(array, 0, newArray, 0, oldCap);
}
}
take
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
//獲取獨佔鎖
lock.lockInterruptibly();
E result;
try {
while ( (result = dequeue()) == null)
notEmpty.await();
} finally {
//釋放獨佔鎖
lock.unlock();
}
return result;
}
//元素出隊列,並調整堆結構
private E dequeue() {
int n = size - 1;
if (n < 0)
return null;
else {
Object[] array = queue;
E result = (E) array[0];
E x = (E) array[n];
array[n] = null;
Comparator<? super E> cmp = comparator;
if (cmp == null)
siftDownComparable(0, x, array, n);
else
siftDownUsingComparator(0, x, array, n, cmp);
size = n;
return result;
}
}
總體來說,PriorityBlockingQueue 也是比較簡單的,內部用數組實現堆結構,通過一個獨佔鎖來控制put和take的線程安全性,通過CAS操作改變allocationSpinLockOffset狀態來達到獲取擴容鎖的目的,這樣擴容操作和讀操作可以同時進行,提高吞吐量。
總結
ArrayBlockingQueue :基於數組結構的有界阻塞隊列,併發控制通過一個ReentrantLock和兩個Condition實現,使用生產者-消費者模式的很好選擇。
LinkedBlockingQueue :基於鏈表結構的無界阻塞隊列,可以設置初始隊列容量使其有界,併發控制通過兩個個ReentrantLock和兩個Condition實現。
SynchronousQueue :本身不帶有空間來存儲任何元素,分爲公平模式和非公平模式兩種,公平模式通過FIFO隊列思想來實現,非公平模式通過棧思想來實現。
PriorityBlockingQueue :帶有優先級的無界阻塞隊列,基於數組實現堆結構,能夠自動擴容。