一. 什麼是隊列
隊列是一種特殊的線性表, 特殊之處在於它只允許在表的前端 (front) 進行刪除操作, 而在表的後端 (rear) 進行插入操作. 和棧一樣, 隊列是一種操作受限制的線性表. 進行插入操作的端稱爲隊尾, 進行刪除操作的端稱爲對頭.
在隊列中插入一個隊列元素稱爲入隊, 從隊列中刪除一個隊列元素稱爲出隊. 因爲隊列只允許在一端插入, 在另一端刪除, 所以只有最早進入隊列的元素才能最先從隊列中刪除. 故隊列又稱爲先進先出 (FIFO - first in first out) 線性表.
二. 什麼是阻塞隊列
簡單來說就是支持阻塞插入和阻塞移除的隊列就可被稱爲阻塞隊列.
阻塞插入
意思是當隊列滿的時候, 隊列會阻塞插入元素的線程, 直到隊列有空缺. 纔可以繼續插入元素.阻塞移除
當隊列爲空的時候, 獲取隊列元素的線程會一直等待隊列變爲非空.
阻塞隊列常用於生產者和消費者的場景, 生產者是向隊列裏添加元素的線程, 消費者是從隊列裏取元素的線程. 阻塞隊列就是生產者用來存放元素, 消費者用來獲取元素的容器.
在線程世界裏, 生產者就是生產數據的線程, 消費者就是消費數據的線程. 在多線程開發中, 如果生產者處理速度很快, 而消費者處理速度很慢, 那麼生產者就必須等待消費者處理完, 才能繼續生產數據. 同樣道理, 如果消費者的處理能力大於生產者, 那麼消費者也就必須等待生產者.
爲了解決這種生產消費能力不均衡的的問題, 便有了生產者和消費者模式. 生產者和消費者通過一個容器來解決生產者和消費者的強耦合問題. 生產者和消費者彼此之間不直接通信, 而是通過阻塞隊列來進行通信, 所以生產者生產完數據之後不用等待消費者處理, 直接丟給阻塞隊列. 消費者則不需要找生產者要數據, 而是直接從阻塞隊列裏取. 阻塞隊列就相當於一個緩衝區, 平衡了生產者和消費者的處理能力.
三. 常用阻塞隊列
JDK 爲我們提供了一個繼承自 Queue
接口的 BlockingQueue
接口 , 常用的阻塞隊列幾乎都實現了此接口. 這些方法並非都是阻塞類的方法, 還包含了非阻塞類的方法. 這兩種方法基本都是成對出現的.例如插入和獲取.
方法/處理方式 | 拋出異常 | 返回特殊值 | 一直阻塞 | 超時退出 |
---|---|---|---|---|
插入方法 | add(E) | offer(E) | put(E) | offer(E, long, TimeUnit) |
移除方法 | remove() | poll() | take() | poll(long, TimeUnit) |
檢查方法 | element() | peek() |
真正體現了阻塞的兩個方法就是 put(E) / take()
拋出異常
當隊列滿的時候, 如果再向隊列中插入元素, 會拋出默認的IllegalStateException
異常.
當隊列爲空時候, 如果再從隊列中獲取元素, 會拋出NoSuchElementException
異常返回特殊值
每次對隊列執行插入操作, 會返回元素是否插入成功, 成功則返回true
.
如果是獲取操作, 則是從隊列中獲取一個元素, 沒有這個元素的話, 返回值爲null
.一直阻塞
當隊列滿的時候, 如果生產者線程繼續向隊列中put
元素, 隊列將會一直阻塞生產者線程, 直到隊列可用或者響應中斷退出.
當隊列爲null
的時候, 如果消費者線程從隊列中take
元素, 隊列會阻塞住消費者線程, 直到隊列不爲null
.超時退出
當阻塞隊列滿時, 如果生產者線程繼續向隊列中插入元素, 隊列會阻塞生產者線程一段時間, 如果超過了這個指定的時間, 生產者線程就會退出.
JDK 中實現了 BlockingQueue
接口的阻塞隊列如下圖所示
-
ArrayBlockingQueue:
一個由數組結構組成的有界阻塞隊列。 -
LinkedBlockingQueue:
一個由鏈表結構組成的有界阻塞隊列。 -
PriorityBlockingQueue:
一個支持優先級排序的無界阻塞隊列。 -
DelayQueue:
一個使用優先級隊列實現的無界阻塞隊列。 -
SynchronousQueue:
一個不存儲元素的阻塞隊列。 -
LinkedTransferQueue:
一個由鏈表結構組成的無界阻塞隊列。 -
LinkedBlockingDeque:
一個由鏈表結構組成的雙向阻塞隊列。
以上的阻塞隊列都實現了 BlockingQueue
接口, 同時也都是線程安全的. (其中 LinkedBlockingDeque
實現了 BlockingDeque
接口. LinkedTransferQueue
實現了 TransferQueue
接口).
有界隊列與無界隊列的概念
有界隊列就是長度有限, 滿了以後生產者就會阻塞. 無界隊列就是隊列中能放無數的東西, 而不會因爲隊列長度限制被阻塞. 當然還是會有限制的, 限制來源於系統資源的限制, 如果處理不及時, 導致隊列越來越大超出一定的限制使內存超限, 會被操作系統或者 JVM 直接殺掉.
其實無界隊列也會阻塞, 因爲阻塞不僅體現在生產者放入元素的時候會阻塞, 消費者拿元素的時候, 如果沒有隊列中沒有元素, 同樣也會阻塞.
1. ArrayBlockingQueue
- 是一個用數組實現的有界阻塞隊列. 此隊列按照先進先出(FIFO) 的原則對元素進行排序. 默認情況下不保證線程公平的訪問隊列, 所謂公平訪問隊列是指阻塞的線程, 可以按照阻塞的先後順序訪問隊列, 即先阻塞線程先訪問隊列. 非公平性是對先等待的線程是非公平的, 當隊列可用時, 阻塞的線程都可以爭奪訪問隊列的資格, 有可能先阻塞的線程最後才訪問隊列. 初始化時有參數可以設置.
2. LinkedBlockingQueue
- 是一個用鏈表實現的有界阻塞隊列, 此隊列的默認和最大長度爲
Integer.MAX_VALUE
. 此隊列按照先進先出的原則對元素進行排序.
3. PriorityBlockingQueue
- 是一個支持優先級的無界阻塞隊列. 默認情況下元素纔去自然順序升序排列. 也可以自定義類實現
compareTo()
方法來指定元素的排序規則, 或者初始化PriorityBlockingQueue
時, 指定構造參數Comparator
來對元素進行排序. 需要注意的是不能保證同優先級元素的順序.
4. DelayQueue
- 是一個支持延遲獲取元素的無界阻塞隊列. 隊列使用
PriorityQueue
來實現. 隊列中的元素必須實現Delayed
接口. 在創建元素時可以指定多久才能從隊列中獲取當前元素. 只有在延遲期滿的時候才能從隊列中提取元素.
5. SynchronousQueue
是一個不存儲元素的阻塞隊列. 每一個
put
操作必須等待一個take
操作. 否則不能繼續添加元素. 可以理解爲SynchronousQueue
負責把生產者線程處理的數據直接傳遞給消費者線程. 隊列本身不存儲任何元素, 適合傳遞類的場景.SynchronousQueue
的吞吐量高於ArrayBlockingQueue
與LinkedBlockingQueue
6. LinkedTransferQueue
多了
transfer
和tryTransfer
方法transfer
: 如果當前有消費者線程正在等待接收數據 (消費者線程使用take()
方法或帶時間限制的poll()
方法時) ,transfer
方法可以把生產者傳入的元素立刻傳遞給消費者線程. 如果沒有消費者在等待接收元素,transfer
方法會將元素存放在隊列的tail
節點, 並等到該元素被消費者消費了才返回.tryTransfer
: 這個方法用來試探生產者傳入的元素是否可以直接傳遞給消費者. 如果沒有消費者等待接收元素則返回false
. 和transfer
方法的區別是tryTransfer
方法無論是否有消費者接收, 方法立即返回. 而transfer
方法是必須等到消費者消費了才返回.
7. LinkedBlockingDeque
是一個鏈表結構的雙向阻塞隊列. 所謂雙向隊列指的是可以從隊列的兩端插入和移除元素. 雙向隊列因爲多了一個操作隊列的入口, 在多線程同時入隊時, 也就減少了一半的競爭.
多了
addFirst
,addLast
,offerFirst
....等方法. 插入方法add
等同於addLast
, 移除remove
等同於removeFirst
. 但是take
方法卻等同於takeFirst
.在初始化
LinkedBlockingDeque
時可以設置其容量防止過度膨脹.
Array 實現的隊列和 Linked 實現隊列的區別
- 隊列中鎖的實現不同
-
ArrayBlockingQueue
實現的隊列中的鎖是沒有分離的, 即生產和消費用的是同一個鎖 -
LinkedBlockingQueue
實現的隊列中的鎖是分離的, 即生產用的是putLock
, 消費是takeLock
.
-
- 在生產或消費時操作不同
-
ArrayBlockingQueue
實現的隊列中在生產和消費的時候, 是直接將枚舉對象插入或移除的. -
LinkedBlockingQueue
實現的隊列中在生產和消費的時候, 需要把枚舉對象轉換爲Node<E>
進行插入或者移除. 會影響性能.
-
- 隊列大小初始化方式不同
-
ArrayBlockingQueue
實現的隊列中必須制定隊列的大小. -
LinkedBlockingQueue
實現的隊列中可以不指定隊列的大小, 但是默認是Integer.MAX_VALUE
.
-
在 Android 開發中會使用到的估計也就只有 ArrayBlockingQueue
與 LinkedBlockingQueue
. 剩下的幾種感興趣的也可以瞭解一下.