對技術還是得有敬畏之心,總覺得Queue好像沒啥,其實只是沒有仔細去了解過。
不過自從上次認真地看了線程池的源代碼之後,發現Queue是一個很神奇的集合類。Queue的形式有無界、有界,還有堵塞、非堵塞。初略想想,這個實現可能就不簡單。
一個問題
在線程池中,自定義線程池時,放入什麼樣的隊列可以讓線程數達不到maximumPoolSize
?
按照線程池的源碼,線程增長有兩個階段:一個階段是在達到corePoolSize
之前,第二個階段是在達到corePoolSize
之後並且阻塞隊列已經滿了,纔會繼續增加線程,直到maximumPoolSize
。
所以解決方案有如下兩種:
①如果隊列是無界的,那麼將不會到達第二階段的增長,也就無法到達maximumPoolSize
;
②還有可以讓corePoolSize
在足夠大的值,同時限定堆大小,保證在有限的任務數下,引發內存不足也算是一種解決方案。
在解決方案①的基礎上,我給出了LinkedBlockingQueue
這個答案,但是它是一個“有界”的堵塞隊列,只是它的capacity默認是Integer.MAX_VALUE
。因爲一開始我得知它是一個無界隊列,因爲未做詳細、深入的瞭解,但是後面我被指正後,引發了我對Queue的思考,因爲不常用,所以瞭解的確不是很多;同時,Queue不僅僅是一個FIFO,還有同步操作在裏面,就是***生產者-消費者***的同步問題的一個解決方案。綜上,這讓我對堵塞隊列產生了強烈的興趣。
概覽
找了一下BlockingQueue大致的實現類,它們的類圖如下。
從中可以看出,它們都繼承自AbstractQueue
,並且都實現了BlockingQueue
接口,只有LinkedTransferQueue
還實現了TransferQueue
。加上對Queue的一些操作並不是特別熟悉,比如offer、peek、poll之類,所以從Queue接口開始,逐個瞭解其大致的功能。
Queue
這個接口定義了6個方法,功能分成3個,分別是插入、刪除、獲取隊首元素,每個功能有2種實現,對應如下表中的關係。這兩種實現的區別也很明顯
Throws exception | Returns special value | |
---|---|---|
Insert | add(e) |
offer(e) |
Remove | remove() |
poll() |
Examine | element() |
peek() |
add(e)
和offer(e)
:添加失敗時,add拋異常,offer返回false。remove()
和poll()
:沒有元素時,remove拋異常,poll返回null。element()
和peek()
:沒有元素時,remove拋異常,peek返回null。
還有一點需要注意,雖然有的Queue的實現(LinkedList
)允許插入null值,但是Queue通常不允許插入null值,因爲null對peek、poll來說,是queue中沒有元素。
BlockingQueue
繼承自Queue
接口,同時在Queue
的基礎上,新增了堵塞插入、堵塞獲取並刪除、可堵塞一定時長的插入、可堵塞一定時長的獲取並刪除。相應關係對應下表。
Throws exception | Special value | Blocks | Times out | |
---|---|---|---|---|
Insert | add(e) |
offer(e) |
put(e) |
offer(e, time, unit) |
Remove | remove() |
poll() |
take() |
poll(time, unit) |
Examine | element() |
peek() |
not applicable | not applicable |
put(e)
和offer(e, time, unit)
:put一直堵塞直到空間可用,offer(e, time, unit)只堵塞特定時長。take()
和poll(time, unit)
: take一直堵塞直到元素可用,poll(time, unit)只堵塞特定時長。
BlockingQueue設計主要被用來處理***生產者-消費者***問題。還有一點需要注意,BlockingQueue
不接受null元素。插入操作***happen-before***讀取、移除。
AbstractQueue
抽象類,繼承自AbstractCollection
,並實現了Queue
接口,但未給出具體實現,相當於引入Queue
相應方法,在自身類中使用,然後靠子類實現Queue
的相關方法。
它實現的add、remove、element方法,分別依靠offer、poll、peek,具體如下:
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
public E element() {
E x = peek();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
public void clear() {
while (poll() != null)
;
}
正好對應於上面Queue方法的區別。
常見實現類
Java中線程安全的內置隊列如下表所示。隊列的底層一般分成三種:數組、鏈表和堆。其中,堆一般情況下是爲了實現帶有優先級特性的隊列。
隊列 | 有界性 | 鎖 | 數據結構 |
---|---|---|---|
ArrayBlockingQueue | bounded | 加鎖 | arraylist |
LinkedBlockingQueue | optionally-bounded | 加鎖 | linkedlist |
ConcurrentLinkedQueue | unbounded | 無鎖 | linkedlist |
LinkedTransferQueue | unbounded | 無鎖 | linkedlist |
PriorityBlockingQueue | unbounded | 加鎖 | heap |
DelayQueue | unbounded | 加鎖 | heap |
基於數組線程安全的隊列,比較典型的是ArrayBlockingQueue
,它主要通過加鎖的方式來保證線程安全;
基於鏈表的線程安全隊列,分成LinkedBlockingQueue
和ConcurrentLinkedQueue
兩大類,前者也通過鎖的方式來實現線程安全,而後者以及上面表格中的LinkedTransferQueue
都是通過原子變量CAS這種不加鎖的方式來實現的。
通過不加鎖的方式實現的隊列都是無界的(無法保證隊列的長度在確定的範圍內);而加鎖的方式,可以實現有界隊列。
在穩定性要求特別高的系統中,爲了防止生產者速度過快,導致內存溢出,只能選擇有界隊列;同時,爲了減少Java的垃圾回收對系統性能的影響,會盡量選擇array/heap格式的數據結構。
Reference:
https://tech.meituan.com/2016/11/18/disruptor.html
https://www.cnblogs.com/WangHaiMing/p/8798709.html
https://www.cnblogs.com/stateis0/p/9062076.html