一.簡介
以下摘自jdk1.8源碼:
A {@link java.util.Queue} that additionally supports operations
that wait for the queue to become non-empty when retrieving an
element, and wait for space to become available in the queue when
storing an element.
- 一個隊列
- 這個隊列要求在隊列非空時取出元素
- 這個隊列要求在隊列非滿時存儲元素
總結:
阻塞隊列是一個隊列。試圖從空的隊列中獲取元素的線程將會被阻塞,直到其他線程往空的隊列插入新的元素。試圖向已滿的隊列中添加新元素的線程將會被阻塞,直到其他線程從隊列中移除一個或多個元素或者完全清空,使隊列變得空閒起來並後續新增
二.源碼:
BlockingQueue是一個接口:
public interface BlockingQueue<E> extends Queue<E>
實現Queue接口,這是一個隊列結構:先進先出。
BlockingQueue的實現類很多
常用的有ArrayBlockingQueue,LinkedBlockingQueue, SynchronousQueue,以下舉例使用ArrayBlockingQueue。
ArrayBlockingQueue 分析
先看一下聲明部分
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
//和ArrayList一樣,底層是用的數組
final Object[] items;
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
注意有兩個Condition一個表示非空,一個表示非滿,還有一個Lock
再看一下核心的方法
put:
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 for waiting puts */
private final Condition notFull;
可以看出put方法的邏輯也是通過Condition配合ReentrantLock實現的,當count和數組長度items.length相等,說明隊列滿,則調用Conditon的await;否則說明有空位,調用enqueue()方法:
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();
}
前幾行都是比較常見的添加元素的方法,注意最後要調用Condition的signal方法,喚醒在notEmpty這個condition上阻塞的線程
take:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
take的邏輯和put基本一樣,當Count=0,當前condition(notEmpty)阻塞,否則返回dequeue(),最後通知生產者,喚醒生產者線程(在notFull處阻塞)
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex]; //取到E
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
/**
@key jdk1.8的新特性迭代器特性,這裏是因爲元素的出隊列所以清理和這個元素相關聯的迭代器
*/
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
三.阻塞隊列的應用
在多線程領域:所謂阻塞,在某些情況下會掛起線程(即阻塞),一旦條件滿足,被掛起的線程又會自動被喚起
- 爲什麼需要BlockingQueue 。好處是我們不需要關心什麼時候需要阻塞線程,什麼時候需要喚醒線程,因爲這一切BlockingQueue都給你一手包辦了
- 在concurrent包發佈以前,在多線程環境下,我們每個程序員都必須去自己控制這些細節,尤其還要兼顧效率和線程安全,而這會給我們的程序帶來不小的複雜度。
- 在生產者消費者模型中,生產數據和消費數據的速率不一致,如果生產數據速度快一些,消費(處理)不過來,就會導致數據丟失。這時候我們就可以應用上阻塞隊列來解決這個問題。
四.API分類
方法類型 | 拋出異常 | 特殊值 | 阻塞 | 超時 |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除 | remove() | poll() | take | poll(time,unit) |
檢查 | element() | peek() | 不可用 | 不可用 |
方法類型 | status |
---|---|
拋出異常 | 當阻塞隊列滿時,再往隊列中add會拋IllegalStateException: Queue full 當阻塞隊列空時,在網隊列裏remove會拋 NoSuchElementException |
特殊值 | 插入方法,成功true失敗false 移除方法,成功返回出隊列的元素,隊列裏沒有就返回null |
一直阻塞 | 當阻塞隊列滿時,生產者線程繼續往隊列裏put元素,隊列會一直阻塞線程知道put數據或響應中斷退出 當阻塞隊列空時,消費者線程試圖從隊列take元素,隊列會一直阻塞消費者線程知道隊列可用。 |
超時退出 | 當阻塞隊列滿時,隊列會阻塞生產者線程一定時間,超過限時後生產者線程會退出 |