阻塞隊列(BlockingQueue)是一個支持兩個附加操作的隊列。這兩個附加的操作是:在隊列爲空時,獲取元素的線程會等待隊列變爲非空。當隊列滿時,存儲元素的線程會等待隊列可用。阻塞隊列常用於生產者和消費者的場景,生產者是往隊列裏添加元素的線程,消費者是從隊列裏拿元素的線程。阻塞隊列就是生產者存放元素到容器,而消費者也只從容器裏拿元素。
四種處理方法
1拋出異常
是指當阻塞隊列已滿時候,如果再往隊列裏插入元素,會拋出IllegalStateException異常。當隊列爲空時,從隊列裏獲取元素時會拋出NoSuchElementException異常 。
2返回特殊值
插入方法會返回是否成功,成功則返回true。移除方法,則是從隊列裏拿出一個元素,如果沒有則返回null。
3一直阻塞
當阻塞隊列已滿時,如果生產者線程往隊列裏put元素,隊列會一直阻塞生產者線程,直到拿到數據,或者響應中斷退出。當隊列爲空時,如果消費者線程從隊列裏take元素,隊列也會阻塞消費者線程,直到隊列可用。
4超時退出
當阻塞隊列已滿時,隊列會阻塞生產者線程一段時間,如果超過一定的時間,生產者線程就會退出。
ArrayBlockingQueue
ArrayBlockingQueue 是一個有界的阻塞隊列,其內部實現是將對象放到一個數組裏。它不能夠存儲無限多數量的元素。一旦創建,就不能更改容量。這個類支持一個可選的公平性策略,用於排序等待的生產者和使用者線程。默認情況下,不設置順序。但是,公平性設置爲true的隊列按FIFO順序授予線程訪問權。公平性通常會降低吞吐量,但會減少可變性並避免飢餓。
DelayQueue
DelayQueue是一個支持延時獲取元素的無界阻塞隊列。隊列使用PriorityQueue來實現。隊列中的元素必須實現Delayed接口,在創建元素時可以指定多久才能從隊列中獲取當前元素。只有在延遲期滿時才能從隊列中提取元素。
LinkedBlockingQueue
LinkedBlockingQueue 內部以一個鏈式結構(鏈接節點)對其元素進行存儲。如果沒有定義上限,將使用 Integer.MAX_VALUE 作爲上限。
PriorityBlockingQueue
PriorityBlockingQueue 是一個支持優先級的無界隊列。它使用了和類 java.util.PriorityQueue 一樣的排序規則。無法向隊列中插入 null 值。所有插入到 PriorityBlockingQueue 的元素必須實現 java.lang.Comparable 接口。因此該隊列中元素的排序就取決於自己的 Comparable 實現。
SynchronousQueue
SynchronousQueue 是一個特殊的隊列,它的內部同時只能夠容納單個元素。如果該隊列已有一個元素的話,試圖向隊列中插入一個新元素的線程將會阻塞,直到另一個線程將該元素從隊列中抽走。同樣,如果該隊列爲空,試圖向隊列中抽取一個元素的線程將會阻塞,直到另一個線程向隊列中插入了一條新的元素。據此,把這個類稱作一個隊列顯然是誇大其詞了。它更多像是一個匯合點。
LinkedTransferQueue
LinkedTransferQueue是基於鏈接節點的無限阻塞隊列。此隊列遵循FIFO(先進先出)。隊列的head是某個生產者在隊列中停留時間最長的元素。隊列的tail是某個生產者在隊列中停留的最短時間的元素。
LinkedBlockingDeque
LinkedBlockingDeque是基於鏈接節點的可選有界的阻塞隊列,可選的容量可用於防止過度擴展。如果未指定,則容量等於Integer.MAX_VALUE。鏈接節點在每次插入時都是動態創建的,除非這會使deque超過容量。
public static void main(String[] args) {
ArrayBlockingQueue<Integer> abq =
new ArrayBlockingQueue<Integer>(3);
new Thread(() -> {
while (true){
try {
abq.put(1);
System.out.println(Thread.currentThread().getName()
+ "放入一個數據");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
while (true){
try {
Integer take = abq.take();
System.out.println(Thread.currentThread().getName()
+ "取出一個數據 " + take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
輸出結果:
Thread-0放入一個數據
Thread-0放入一個數據
Thread-0放入一個數據
Thread-1取出一個數據 1
Thread-1取出一個數據 1
Thread-1取出一個數據 1
Thread-1取出一個數據 1
Thread-0放入一個數據
Thread-0放入一個數據
Thread-1取出一個數據 1
Thread-1取出一個數據 1
Thread-0放入一個數據
Thread-0放入一個數據
Thread-1取出一個數據 1
Thread-0放入一個數據
...
看到這個輸出結果,你可能會有所疑問,好像放入和取出並不配對,這是因爲put、take和System.out.println並非原子操作。
阻塞隊列原理
Java中使用通知模式實現阻塞隊列的通訊。就是當生產者往滿的隊列裏添加元素時會阻塞住生產者,並通知消費者可以取出元素了,當消費者消費了一個隊列中的元素後,會通知生產者可以放入元素了。在ArrayBlockingQueue中 使用了 Condition 來實現。
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
/** 隊列元素 */
final Object[] items;
/** items index for next take, poll, peek or remove */
int takeIndex;
/** items index for next put, offer, or add */
int putIndex;
/** Number of elements in the queue */
int count;
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
...
}
put(E e)
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 隊列已經放滿了
while (count == items.length)
notFull.await();
// 沒放滿,則放入隊列
enqueue(e);
} finally {
lock.unlock();
}
}
當隊列已經放滿的時候,就會調用Condition的await方法就會阻塞當前線程,讓生產者等待。如果沒放滿,則調用enqueue方法加入隊列並釋放消費者線程。
enqueue(e)
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
// 當隊列放滿的時候,putIndex重新從 0 開始
if (++putIndex == items.length)
putIndex = 0;
count++;
// 喚醒消費者線程
notEmpty.signal();
}
putIndex和takeIndex分別對應放入和取出的遊標.
ArrayBlockingQueue<Integer> abq =
new ArrayBlockingQueue<Integer>(3);
表示item數組的長度爲3,當放入第一個元素的時候,putIndex爲0,放入第二個元素的時候,putIndex爲1,以此類推,當我們放入第三個的時候,putIndex爲2,那麼++putIndex爲3,此時數組已滿,則將putIndex歸0,因爲我們調用take的時候,takeIndex也是從0開始的。
take()
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
dequeue()
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;
}
當取出一個元素時,同時喚醒生產者線程。
更多精彩內容請關注微信公衆號: