阻塞佇列 BlockingQueue
執行緒安全的佇列訪問
主要應用場景:生產者消費者模型,是執行緒安全的
阻塞情況:
- 當佇列滿了進行入隊操作
- 當佇列空了的時候進行出佇列操作
4種處理方式
- (Throws Exceptions) 丟擲異常:當佇列滿時,如果再往佇列裡插入元素,會丟擲IllegalStateException(“Queue full”)異常。當佇列空時,從佇列裡獲取元素會丟擲NoSuchElementException異常。
- (Special Value)返回特殊值:當往佇列插入元素時,會返回元素是否插入成功,成功返回true。如果是移除方法,則是從佇列裡取出一個元素,如果沒有則返回null。
- (Blocks)一直阻塞:當阻塞佇列滿時,如果生產者執行緒往佇列裡put元素,佇列會一直阻塞生產者執行緒,直到佇列可用或者響應中斷退出。當佇列空時,如果消費者執行緒從佇列裡take元素,佇列會阻塞住消費者執行緒,直到佇列不為空。
- (Times Out)超時退出:當阻塞佇列滿時,如果生產者執行緒往佇列裡插入元素,佇列會阻塞生產者執行緒一段時間,如果超過了指定的時間,生產者執行緒就會退出。
注意: 如果是無界阻塞佇列,佇列不可能會出現滿的情況,所以使用put或offer方法永遠不會被阻塞,而且使用offer方法時,該方法永遠返回true。
實現類:
Java併發包中的7個阻塞佇列:
佇列 | 有界性 | 鎖 | 特點 |
---|---|---|---|
ArrayBlockingQueue | bounded(有界) | 加鎖 | 陣列 FIFO |
LinkedBlockingQueue | 指定-有界、不指定-無界 | 加鎖 | 連結串列 FIFO |
PriorityBlockingQueue | 無界 | 加鎖 | 優先順序排序(預設升序) |
DelayQueue | 無界 | 加鎖 | 延時獲取 使用PriorityQueue實現 |
SynchronousQueue | bounded | 加鎖 | 不儲存元素 |
LinkedTransferQueue | 無界 | 加鎖 | 連結串列 (傳輸) |
LinkedBlockingDeque | 無界 | 無鎖 | 連結串列 雙向 |
ArrayBlockingQueue
初始化時指定容量大小,一旦指定大小就不能再變。採用FIFO方式儲存元素。
- 預設情況下不保證執行緒公平的訪問佇列。
- 公平訪問佇列是指阻塞的執行緒,可以按照阻塞的先後順序訪問佇列,即先阻塞執行緒先訪問佇列。
- 非公平性是對先等待的執行緒是非公平的,當佇列可用時,阻塞的執行緒都可以爭奪訪問佇列的資格,有可能先阻塞的執行緒最後才訪問 佇列。
- 為了保證公平性,通常會降低吞吐量。
使用以下程式碼建立一個公平的阻塞佇列。
ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true);
訪問者的公平性是使用可重入鎖實現的,程式碼如下。
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);//初始化ReentrantLock重入鎖,出隊入隊擁有這同一個鎖
notEmpty = lock.newCondition;//初始化非空等待佇列
notFull = lock.newCondition;//初始化非滿等待佇列
}
LinkedBlockingQueue
大小配置可選,如果初始化時指定了大小,那麼它就是有邊界的。不指定就無邊界(最大整型值)。內部實現是連結串列,採用FIFO形式儲存資料。
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);//不指定大小,無邊界採用預設值,最大整型值
}
PriorityBlockingQueue
帶優先順序的阻塞佇列(預設升序)。無邊界佇列,允許插入null
。插入的物件必須實現Comparator介面, 對Comparable介面的實現來指定佇列優先順序的排序規則。我們可以從PriorityBlockingQueue中獲取一個迭代器,但這個迭代器並不保證能按照優先順序的順序進行迭代。
public boolean add(E e) {//新增方法
return offer(e);
}
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock();
int n, cap;
Object[] array;
while ((n = size) >= (cap = (array = queue).length))
tryGrow(array, cap);
try {
Comparator<? super E> cmp = comparator;//必須實現Comparator介面
if (cmp == null)
siftUpComparable(n, e, array);
else
siftUpUsingComparator(n, e, array, cmp);
size = n + 1;
notEmpty.signal();
} finally {
lock.unlock();
}
return true;
}
SynchronusQueue
不儲存元素,非常適合傳遞性場景,吞吐量較高。每一個put操作必須等待一個take操作,否則不能繼續新增元素。
它支援公平訪問佇列。預設情況下執行緒採用非公平性策略訪問佇列。
//建立公平性訪問 fair=true,則等待的執行緒會採用先進先出的順序訪問佇列。
public SynchronousQueue(boolean fair) {
transferer = fair?new TransferQueue() : new TransferStack();
}
LinkedTransferQueue
相對於其他阻塞佇列,LinkedTransferQueue多了tryTransfer和transfer方法。
(1)transfer方法
- 如果當前有消費者正在等待接收元素(消費者使用take()方法或帶時間限制的poll()方法時),transfer方法可以把生產者傳入的元素立刻transfer(傳輸)給消費者。
- 如果沒有消費者在等待接收元素,transfer方法會將元素存放在佇列的tail節點,並等到該元素被消費者消費了才返回。
//試圖把存放當前元素的s節點作為tail節點
Node pred = tryAppend(s, haveData);
//CPU自旋等待消費者消費元素。
//因為自旋會消耗CPU,所以自旋一定的次數後使用Thread.yield()方法來暫停當前正在執行的執行緒,並執行其他執行緒。
return awaitMatch(s, pred, e, (how == TIMED), nanos);
(2)tryTransfer方法
- tryTransfer方法是用來試探生產者傳入的元素是否能直接傳給消費者。如果沒有消費者等待接收元素,則返回false。
- 對於帶有時間限制的tryTransfer(E e,long timeout,TimeUnit unit)方法,試圖把生產者傳入的元素直接傳給消費者,但是如果沒有消費者消費該元素則等待指定的時間再返回,如果超時還沒消費元素,則返回false,如果在時限內消費了元素,則返回true。
(3)區別
tryTransfer方法無論消費者是否接收,方法立即返回,而transfer方法是必須等到消費者消費了才返回。
LinkedBlockingDeque
LinkedBlockingDeque是一個由連結串列結構組成的雙向阻塞佇列(可以從佇列的兩端插入和移出元素,減少了一半的競爭)。
相比其他的阻塞佇列,LinkedBlockingDeque多了
addFirst、offerFirst、peekFirst
addLast、offerLast、peekLast 等方法
以First單詞結尾的方法,表示插入、獲取(peek)或移除雙端佇列的第一個元素。
以Last單詞結尾的方法,表示插入、獲取(peek)或移除雙端佇列的最後一個元素。
插入方法add等同於addLast
移除方法remove等效於removeFirst,但是take方法卻等同於takeFirst。
使用時還是用帶有First和Last字尾的方法更清楚。
在初始化LinkedBlockingDeque時可以設定容量防止其過度膨脹。另外,雙向阻塞佇列可以運用在“工作竊取
”模式中。
阻塞佇列的實現原理
使用通知模式實現:當生產者往滿的佇列裡新增元素時會阻塞住生產者,當消費者消費了一個佇列中的元素後,會通知生產者當前佇列可用。(JDK原始碼ArrayBlockingQueue使用了Condition來實現)
當往佇列裡插入一個元素時,如果佇列不可用,那麼阻塞生產者主要通過LockSupport.park(this)來實現。
參考:
慕課網實戰·高併發探索(十三):併發容器J.U.C – 元件FutureTask、ForkJoin、BlockingQueue
阻塞佇列 BlockingQueue
BlockingQueue深入解析
《java併發程式設計的藝術》
相關文章
- 阻塞佇列BlockingQueue(三)--DelayQueue佇列BloC
- 阻塞佇列 BlockingQueue(二)--ArrayBlockingQueue與LinkedBlockingQueue佇列BloC
- Java併發系列 — 阻塞佇列(BlockingQueue)Java佇列BloC
- Java 阻塞佇列(BlockingQueue)的內部實現原理Java佇列BloC
- Java BlockingQueue 阻塞佇列[用於多執行緒]JavaBloC佇列執行緒
- Java併發指南11:解讀 Java 阻塞佇列 BlockingQueueJava佇列BloC
- 佇列、阻塞佇列佇列
- BlockingQueue的作用以及實現的幾個常用阻塞佇列原理BloC佇列
- 阻塞佇列一——java中的阻塞佇列佇列Java
- 阻塞佇列--LinkedBlockingQueue佇列BloC
- 解讀 Java 併發佇列 BlockingQueueJava佇列BloC
- java併發程式設計工具類JUC第一篇:BlockingQueue阻塞佇列Java程式設計BloC佇列
- Java併發包原始碼學習系列:阻塞佇列BlockingQueue及實現原理分析Java原始碼佇列BloC
- 死磕阻塞佇列佇列
- 延遲阻塞佇列 DelayQueue佇列
- Java中的阻塞佇列Java佇列
- 阻塞佇列——四組API佇列API
- 乾貨|解讀Java併發佇列BlockingQueueJava佇列BloC
- 阻塞佇列 SynchronousQueue 原始碼解析佇列原始碼
- 阻塞佇列 LinkedTransferQueue 原始碼解析佇列原始碼
- 阻塞佇列 DelayQueue 原始碼解析佇列原始碼
- 阻塞佇列 PriorityBlockingQueue 原始碼解析佇列BloC原始碼
- 聊聊併發(四)——阻塞佇列佇列
- [Java併發程式設計實戰] 阻塞佇列 BlockingQueue(含程式碼,生產者-消費者模型)Java程式設計佇列BloC模型
- Java併發——阻塞佇列集(上)Java佇列
- Java併發——阻塞佇列集(下)Java佇列
- 處理線上RabbitMQ佇列阻塞MQ佇列
- 什麼是阻塞佇列?如何使用阻塞佇列來實現生產者-消費者模型?佇列模型
- Java併發程式設計:阻塞佇列Java程式設計佇列
- Android併發學習之阻塞佇列Android佇列
- JAVA併發之阻塞佇列淺析Java佇列
- JAVA中常見的阻塞佇列詳解Java佇列
- Java併發程式設計——阻塞佇列Java程式設計佇列
- 『併發包入坑指北』之阻塞佇列佇列
- 迴歸Java基礎:LinkedBlockingQueue阻塞佇列解析JavaBloC佇列
- 執行緒池的阻塞佇列的理解執行緒佇列
- Java併發6:阻塞佇列,Fork/Join框架Java佇列框架
- JDK中有直接可以使用的阻塞佇列JDK佇列