阻塞隊列,關鍵字是阻塞,先理解阻塞的含義,在阻塞隊列中,線程阻塞有這樣的兩種情況:
- 當隊列中沒有數據的情況下,消費者端的所有線程都會被自動阻塞(掛起),直到有數據放入隊列。
- 當隊列中填滿數據的情況下,生產者端的所有線程都會被自動阻塞(掛起),直到隊列中有空的位置,線程被自動喚醒。
阻塞隊列的主要方法:
方法類型
|
拋出異常
|
特殊值(null 或 false,視情況而定)
|
阻塞
|
超時(放棄前只在最大的時間內阻塞)
|
插入
|
add(e)
|
offer(e)
|
put(e)
|
offer(e,time,unit)
|
移除
|
remove()
|
poll()
|
take()
|
poll(time,unit)
|
獲取
|
element()
|
peek()
|
\
|
\
|
Java 中的阻塞隊列
1. ArrayBlockingQueue :由數組結構組成的有界阻塞隊列。
2. LinkedBlockingQueue :由鏈表結構組成的有界阻塞隊列。
3. PriorityBlockingQueue :支持優先級排序的無界阻塞隊列。
4. DelayQueue:使用優先級隊列實現的無界阻塞隊列。
5. SynchronousQueue:不存儲元素的阻塞隊列。
6. LinkedTransferQueue:由鏈表結構組成的無界阻塞隊列。
7. LinkedBlockingDeque:由鏈表結構組成的雙向阻塞隊列
1、ArrayBlockingQueue(公平、非公平)
用數組實現的有界阻塞隊列。此隊列按照先進先出(FIFO)的原則對元素進行排序。默認情況下不保證訪問者公平的訪問隊列,所謂公平訪問隊列是指阻塞的所有生產者線程或消費者線程,當隊列可用時,可以按照阻塞的先后順序訪問隊列,即先阻塞的生產者線程,可以先往隊列里插入元素,先阻塞的消費者線程,可以先從隊列里獲取元素。通常情況下為了保證公平性會降低吞吐量。我們可以使用以下代碼創建一個公平的阻塞隊列:

ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true);
2、LinkedBlockingQueue(兩個獨立鎖提高並發)
基於鏈表的阻塞隊列,同ArrayListBlockingQueue類似,此隊列按照先進先出(FIFO)的原則對元素進行排序。而 LinkedBlockingQueue 之所以能夠高效的處理並發數據,還因為其對於生產者端和消費者端分別采用了獨立的鎖來控制數據同步,這也意味着在高並發的情況下生產者和消費者可以並行地操作隊列中的數據,以此來提高整個隊列的並發性能。
- LinkedBlockingQueue里面有兩把鎖,而ArrayBlockingQueue只有一把,但是他們都有兩個Condition對象,LinkedBlockingQueue里面的兩個Condition對象來自不同的鎖,而ArrayBlockingQueue的兩個Condition對象來自同一個鎖;(他們的兩個Condition對象的作用都相同,一個用於維護消費者,一個用於維護生產者);
- ArrayBlockingQueue里面生產者每生產一個數據就會通知消費者來取,消費者每取走一個數據就會通知生產者來生產;而LinkedBlockingQueue里面的只要生產者生產后倉庫還有空間,就通知后面的其他生產者繼續生產,並且如果倉庫有一個數據,還會喚醒消費者消費;而對於它的消費者也一樣,只要前一個消費者取出數據后倉庫還有數據,就通知其他消費者繼續消費,如果倉庫有一個空位,就通知生產者生產;(因為用到兩把鎖,所以LinkedBlockingQueue的消費者、生產者之間是並發的(你生產你的,我消費我的,而ArrayBlockingQueue只有一把鎖,生產的時候只能是生產者生產,消費的時候只能是消費者消費);
LinkedBlockingQueue 會默認一個類似無限大小的容量(Integer.MAX_VALUE)。
3、PriorityBlockingQueue(compareTo 排序實現優先)
是一個支持優先級的無界隊列。默認情況下元素采取自然順序升序排列。可以自定義實現compareTo()方法來指定元素進行排序規則,或者初始化 PriorityBlockingQueue 時,指定構造參數 Comparator 來對元素進行排序。需要注意的是不能保證同優先級元素的順序;
4、DelayQueue(緩存失效、定時任務 )
是一個支持延時獲取元素的無界阻塞隊列。隊列使用PriorityQueue來實現。隊列中的元素必須實現 Delayed 接口,在創建元素時可以指定多久才能從隊列中獲取當前元素。只有在延遲期滿時才能從隊列中提取元素。我們可以將 DelayQueue 運用在以下應用場景:
- 緩存系統的設計:可以用 DelayQueue 保存緩存元素的有效期,使用一個線程循環查詢DelayQueue,一旦能從 DelayQueue 中獲取元素時,表示緩存有效期到了。
- 定時任務調度:使用 DelayQueue 保存當天將會執行的任務和執行時間,一旦從DelayQueue 中獲取到任務就開始執行,從比如 TimerQueue 就是使用 DelayQueue 實現的。
5、SynchronousQueue(不存儲數據、可用於傳遞數據)
是一個不存儲元素的阻塞隊列。每一個 put 操作必須等待一個 take 操作,否則不能繼續添加元素。SynchronousQueue 可以看成是一個傳球手,負責把生產者線程處理的數據直接傳遞給消費者線程,隊列本身並不存儲任何元素,非常適合於傳遞性場景,比如在一個線程中使用的數據,傳遞給另 外 一 個 線 程 使 用 , SynchronousQueue 的 吞 吐 量 高 於 LinkedBlockingQueue 和ArrayBlockingQueue。
6、LinkedTransferQueue
是 一 個 由 鏈 表 結 構 組 成 的 無 界 阻 塞 TransferQueue 隊 列 。 相 對 於 其 他 阻 塞 隊 列 ,LinkedTransferQueue 多了 tryTransfer 和 transfer 方法。
- transfer 方法:如果當前有消費者正在等待接收元素(消費者使用 take()方法或帶時間限制的poll()方法時),transfer 方法可以把生產者傳入的元素立刻 transfer(傳輸)給消費者。如果沒有消費者在等待接收元素,transfer 方法會將元素存放在隊列的 tail 節點,並等到該元素被消費者消費了才返回。
- tryTransfer 方法。則是用來試探下生產者傳入的元素是否能直接傳給消費者。如果沒有消費者等待接收元素,則返回 false。和 transfer 方法的區別是 tryTransfer 方法無論消費者是否接收,方法立即返回。而 transfer 方法是必須等到消費者消費了才返回。
對於帶有時間限制的 tryTransfer(E e, long timeout, TimeUnit unit)方法,則是試圖把生產者傳入的元素直接傳給消費者,但是如果沒有消費者消費該元素則等待指定的時間再返回,如果超時還沒消費元素,則返回 false,如果在超時時間內消費了元素,則返回 true。
7、LinkedBlockingDeque
是一個由鏈表結構組成的雙向阻塞隊列。所謂雙向隊列指的你可以從隊列的兩端插入和移出元素。雙端隊列因為多了一個操作隊列的入口,在多線程同時入隊時,也就減少了一半的競爭。相比其他的阻塞隊列,LinkedBlockingDeque 多了 addFirst,addLast,offerFirst,offerLast,peekFirst,peekLast 等方法,以 First 單詞結尾的方法,表示插入,獲取(peek)或移除雙端隊列的第一個元素。以 Last 單詞結尾的方法,表示插入,獲取或移除雙端隊列的最后一個元素。另外插入方法 add 等同於 addLast,移除方法 remove 等效於 removeFirst。但是 take 方法卻等同於 takeFirst,不知道是不是 Jdk 的 bug,使用時還是用帶有 First 和 Last 后綴的方法更清楚。
在初始化 LinkedBlockingDeque 時可以設置容量防止其過渡膨脹。另外雙向阻塞隊列可以運用在“工作竊取”模式中(就是一個線程從前往后執行任務,另外一個線程從后往前執行)。