在講《21張圖講解集合的線程不安全》那一篇,我留了一個彩蛋,就是Queue(隊列)尚未講,此次咱們重點來看看Java中的Queue家族,總共涉及到18種Queue。這篇恐怕是市面上最全最細 講解Queue的。java
本篇主要內容以下:程序員
幫你總結好的阻塞隊列:面試
hi,你們好,個人英文名叫Queue
,中文名叫隊列
,不管現實生活中仍是計算機的世界中,我都是一個很重要的角色哦~算法
我是一種數據結構
,你們能夠把我想象成一個數組,元素從個人一頭進入、從另一頭出去,稱爲FIFO原則(先進先出原則)。編程
我還有兩個親兄弟:List
(列表)、Set
(集),他們都是Collection
的兒子,我還有一個遠房親戚:Map
(映射)。他們都是java.util
包這個你們庭的成員哦~小程序
18種隊列分爲三大類: 接口、抽象類、普通類。數組
弄清楚下面的繼承實現關係對後面理解18種隊列有很大幫助。緩存
Queue
接口繼承 Collection
接口,Collection
接口繼承 Iterable
接口BlockingQueue
接口、Deque
接口 繼承 Queue
接口AbstractQueue
抽象類實現 Queue
接口BlockingDeque
接口、TransferQueue
接口繼承 BlockingQueue
接口BlockingDeque
接口繼承Deque
接口LinkedBlockingDeque
類實現 BlockingDeque
接口LinkedTransferQueue
類接口實現 TransferQueue
接口LinkedList
類、ArrayDeque
類、ConcurrentLinkedDeque
類實現 了Deque
接口ArrayBlockingQueue
類、LinkendBlockingQueue
類、LinkedBlockingDeque
類、LinkedTransferQueue
類、SynchronousQueue
類、PriorityBlockQueue
類、DelayQueue類
繼承 了AbstractQueue
抽象類和實現了BlockingQueue接口PriorityQueue
類和ConcurrentLinkedQueue
類繼承 了AbstractQueue
抽象類注意:安全
Queue接口是一種Collection,被設計用於處理以前臨時保存在某處的元素。markdown
除了基本的Collection操做以外,隊列還提供了額外的插入、提取和檢查操做。每一種操做都有兩種形式:若是操做失敗,則拋出一個異常;若是操做失敗,則返回一個特殊值(null或false,取決因而什麼操做)。
隊列一般是以FIFO(先進先出)的方式排序元素,可是這不是必須的。
只有優先級隊列能夠根據提供的比較器對元素進行排序或者是採用正常的排序。不管怎麼排序,隊列的頭將經過調用remove()或poll()方法進行移除。在FIFO隊列種,全部新的元素被插入到隊尾。其餘種類的隊列可能使用不一樣的佈局來存放元素。
每一個Queue必須指定排序屬性。
總共有3組方法,每一組方法對應兩個方法。以下圖所示:
動做 | 拋異常 | 返回特殊值 |
---|---|---|
Insert | add(e) | offer(e) |
Remove | remove() | poll |
Examine | element() | peek() |
(1)好比添加(Insert)
元素的動做,會有兩種方式:add(e)
和offer(e)
。若是調用add(e)方法時,添加失敗,則會拋異常
,而若是調用的是offer(e)方法失敗時,則會返回false
。offer方法用於異常是正常的狀況下使用,好比在有界隊列中,優先使用offer方法。假如隊列滿了,不能添加元素,offer方法返回false,這樣咱們就知道是隊列滿了,而不是去handle運行時拋出的異常。
(2)同理,移除(Remove)元素的動做,隊列爲空時,remove方法拋異常,而poll返回null。若是移除頭部的元素成功,則返回移除的元素。
(3)同理,檢測(Examine)元素的動做,返回頭部元素(最開始加入的元素),但不刪除元素, 若是隊列爲空,則element()方法拋異常,而peek()返回false。
(4)Queue接口沒有定義阻塞隊列的方法,這些方法在BlockQueue接口中定義了。
(5)Queue實現類一般不容許插入null元素,儘管一些實現類好比LinkedList不由止插入null,可是仍是不建議插入null,由於null也被用在poll方法的特殊返回值,以說明隊列不包含元素。
(1)Deque概念: 支持兩端元素插入和移除的線性集合。名稱deque
是雙端隊列的縮寫,一般發音爲deck
。大多數實現Deque的類,對它們包含的元素的數量沒有固定的限制的,支持有界和無界。
(2)Deque方法說明:
**說明: **
該列表包含包含訪問deque兩端元素的方法,提供了插入,移除和檢查元素的方法。
這些方法種的每一種都存在兩種形式:若是操做失敗,則會拋出異常,另外一種方法返回一個特殊值(null或false,取決於具體操做)。
插入操做的後一種形式專門設計用於容量限制的Deque實現,大多數實現中,插入操做不能失敗,因此能夠用插入操做的後一種形式。
Deque接口擴展了Queue接口,當使用deque做爲隊列時,做爲FIFO。元素將添加到deque的末尾,並從頭開始刪除。
做爲FIFO時等價於Queue的方法以下表所示:
好比Queue的add方法和Deque的addLast方法等價。
Deque也能夠用做LIFO(後進先出)棧,這個接口優於傳統的Stack類。看成爲棧使用時,元素被push到deque隊列的頭,而pop也是從隊列的頭pop出來。
Stack(棧)的方法正好等同於Deque的以下方法:
注意:peek方法不管是做爲棧仍是隊列,都是從隊列的檢測隊列的頭,返回最早加入的元素。好比第一次put 100,第二次put 200,則peek返回的是100。以下圖所示:
AbstractQueue是一個抽象類,繼承了Queue接口,提供了一些Queue操做的骨架實現。
方法add、remove、element方法基於offer、poll和peek。也就是說若是不能正常操做,則拋出異常。咱們來看下AbstactQueue是怎麼作到的。
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();
}
複製代碼
注意:
ArrayBlockingQueue
類、LinkendBlockingQueue
類、LinkedBlockingDeque
類、LinkedTransferQueue
類、SynchronousQueue
類、PriorityBlockQueue
類、DelayQueue類
繼承 了AbstractQueue
抽象類PriorityQueue
類和ConcurrentLinkedQueue
類繼承 了AbstractQueue
抽象類(1)BlockingQueue(阻塞隊列)也是一種隊列,支持阻塞的插入和移除方法。
(3)阻塞的插入:當隊列滿時,隊列會阻塞插入元素的線程,直到隊列不滿。
(4)阻塞的移除:當隊列爲空,獲取元素的線程會等待隊列變爲非空。
(5)應用場景:生產者和消費者,生產者線程向隊列裏添加元素,消費者線程從隊列裏移除元素,阻塞隊列時獲取和存放元素的容器。
(6)爲何要用阻塞隊列:生產者生產和消費者消費的速率不同,須要用隊列來解決速率差問題,當隊列滿了或空的時候,則須要阻塞生產或消費動做來解決隊列滿或空的問題。
線程A往阻塞隊列(Blocking Queue)中添加
元素,而線程B從阻塞隊列中移除
元素。
BlockingQueue接口的10個核心方法:
10個核心方法總結以下:
有三大類操做:插入、移除、檢查。
IllegalStateException
- 隊列滿了ClassCastException
- 添加的元素類型不匹配NullPointerException
- 添加的元素爲nullIllegalArgumentException
- 添加的元素某些屬性不匹配NoSuchElementException
- 若是這個隊列是空的是阻塞隊列BlockingQueue
和雙向隊列Deque
接口的結合。有以下方法:
示例:
嘗試執行如下方法:
LinkedBlockingDeque queue = new LinkedBlockingDeque();
queue.addFirst("test1");
queue.addFirst(300);
queue.addLast("400");
複製代碼
最後隊列中的元素順序以下:
300, test1, 400。
先添加了test1放到隊列的頭部,而後在頭部的前面放入300,因此300在最前面,成爲頭部,而後將400放入隊列的尾部,因此最後的結果是300, test1, 400。
若是有消費者正在獲取元素,則將隊列中的元素傳遞給消費者。若是沒有消費者,則等待消費者消費。我把它稱做使命必達隊列,必須將任務完成才能返回。
transfer(E e)
原理以下圖所示:
tryTransfer(E e)
tryTransfer(E e, long timeout, TimeUnit unit)
getWaitingConsumerCount()
hasWaitingConsumer()
PriorityQueue是一個支持優先級的無界阻塞隊列。
默認天然順序升序排序。
能夠經過構造參數Comparator來對元素進行排序。
public PriorityQueue(Comparator<? super E> comparator) {
this(DEFAULT_INITIAL_CAPACITY, comparator);
}
複製代碼
public Comparator<? super E> comparator() {
return comparator;
}
複製代碼
Arrays.sort(pq.toArray)
。offer
、add
)和出列( poll
、remove()
)的時間複雜度O(log(n))。咱們來看下節點類Node
private static class Node<E> {
E item; //元素
Node<E> next; //向後的節點連接
Node<E> prev; //向前的節點連接
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
複製代碼
1.LinkedList的增長和刪除效率相對較高,而查找和修改的效率相對較低。
2.如下狀況建議使用ArrayList
3.如下狀況建議使用LinkedList
LinkedList不是線程安全的,因此可使用以下方式保證線程安全。
List list = Collections.synchronizedList(new LinkedList<>());
複製代碼
LinkedList 繼承了 AbstractSequentialList 類。
LinkedList 實現了 Queue 接口,可做爲隊列使用。
LinkedList 繼承了 AbstractQueue抽象類,具備隊列的功能。
LinkedList 實現了 List 接口,可進行列表的相關操做。
LinkedList 實現了 Deque 接口,可做爲雙向隊列使用。
LinkedList 實現了 Cloneable 接口,可實現克隆。
LinkedList 實現了 java.io.Serializable 接口,便可支持序列化,能經過序列化去傳輸。
ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();
BuildingBlockWithName buildingBlock = new BuildingBlockWithName("三角形", "A");
concurrentLinkedQueue.add(buildingBlock);
複製代碼
建立一個ArrayDeque,往arrayDeque隊尾添加元素。
ArrayDeque arrayDeque = new ArrayDeque();
for (int i = 0; i < 50; i++) {
arrayDeque.add(buildingBlock); // add方法等價於addLast方法
}
複製代碼
建立兩個積木:三角形、四邊形,而後添加到隊列:
BuildingBlockWithName buildingBlock1 = new BuildingBlockWithName("三角形", "A");
BuildingBlockWithName buildingBlock2 = new BuildingBlockWithName("四邊形", "B");
ConcurrentLinkedDeque concurrentLinkedDeque = new ConcurrentLinkedDeque();
concurrentLinkedDeque.addFirst(buildingBlock1);
concurrentLinkedDeque.addLast(buildingBlock2);
//結果:順序:三角形、四邊形
複製代碼
建立兩個積木:三角形、四邊形,而後添加到隊列:
BuildingBlockWithName buildingBlock1 = new BuildingBlockWithName("三角形", "A");
BuildingBlockWithName buildingBlock2 = new BuildingBlockWithName("四邊形", "B");
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(100, true);
arrayBlockingQueue.add(buildingBlock1);
arrayBlockingQueue.add(buildingBlock2);
複製代碼
LinkedList linkedList1 = new LinkedList();
linkedList1.add("A");
linkedList1.add("B");
linkedList1.add("C");
複製代碼
LinkedBlockingDeque能夠用在「工做竊取「模式中。
工做竊取算法
:某個線程比較空閒,從其餘線程的工做隊列中的隊尾竊取任務來幫忙執行。
LinkedTransferQueue = 阻塞隊列+鏈表結構+TransferQueue
以前咱們講「使命必達TransferQueue接口時已經介紹過了TransferQueue接口 ,因此LinkedTransferQueue接口跟它類似,只是加入了阻塞插入和移除的功能,以及結構是鏈表結構。
以前的TransferQueue也講到了3個案例來講明TransferQueue的原理,你們能夠回看TransferQueue。
生產者1 transfer A
生產者2 transfer D
2s後...
消費者 take A
生產者1 put B
2s後...
消費者 take D
生產者2 transfer E
2s後...
消費者 take B
生產者1 transfer C
複製代碼
(1)首先生產者線程1和2 調用transfer方法來傳輸A和D,發現沒有消費者線程接收,因此被阻塞。
(2)消費者線程過了2s後將A拿走了,而後生產者1 被釋放繼續執行,傳輸元素B,發現又沒有消費者消費,因此進行了等待。
(3)消費者線程過了2s後,將排在隊列首部的D元素拿走,生產者2繼續往下執行,傳輸元素E,發現沒有消費者,因此進行了等待。
(4)消費者線程過了2s後,將排在隊列首部的B元素拿走,生產者1傳輸C元素,等待消費者拿走。
(5)消費者再也不消費了,因此生產者1和生產者2都被阻塞了,元素C和,元素E都沒有被拿走,並且生產者2的F元素尚未開始傳輸,由於在等待元素D被拿走。
(6)看下隊列裏面確實有C和E元素,並且E排在隊列的首部。
我稱SynchronousQueue爲」傳球好手「。想象一下這個場景:小明抱着一個籃球想傳給小花,若是小花沒有將球拿走,則小明是不能再拿其餘球的。
SynchronousQueue負責把生產者產生的數據傳遞給消費者線程。
SynchronousQueue自己不存儲數據,調用了put方法後,隊列裏面也是空的。
每個put操做必須等待一個take操做完成,不然不能添加元素。
適合傳遞性場景。
性能高於ArrayBlockingQueue和LinkedBlockingQueue。
咱們建立了兩個線程,一個線程用於生產,一個線程用於消費
t1 put A
t2 take A
5秒後...
t1 put B
t2 take B
5秒後...
t1 put C
t2 take C
複製代碼
小結:說明生產線程執行put第一個元素"A" 操做後,須要等待消費者線程take完「A」後,才能繼續往下執行代碼。
public class DelayQueue<E extends Delayed> extends AbstractQueue<E> implements BlockingQueue<E> {
複製代碼
public boolean offer(E e, long timeout, TimeUnit unit) {
return offer(e);
}
複製代碼
if (first == null || first.getDelay(NANOSECONDS) > 0)
return null;
else
return q.poll();
複製代碼
緩存系統的設計:能夠用DelayQueue保存緩存元素的有效期。而後用一個線程循環的查詢DelayQueue隊列,一旦能從DelayQueue中獲取元素時,表示緩存有效期到了。
定時任務調度:使用DelayQueue隊列保存當天將會執行的任務和執行時間,一旦從DelayQueue中獲取到任務就開始執行。好比Java中的TimerQueue就是使用DelayQueue實現的。
這一篇花了不少心思在上面,看官方英文文檔、畫原理圖、寫demo代碼,排版。這恐怕是市面上最全最細講解Queue的。
❝
你好,我是
悟空哥
,「7年項目開發經驗,全棧工程師,開發組長,超喜歡圖解編程底層原理」。正在編寫兩本PDF,分別是 一、Spring Cloud實戰項目(佳必過),二、Java併發必知必會。我還手寫了2個小程序
,Java刷題小程序,PMP刷題小程序,點擊個人公衆號菜單打開!另外有111本架構師資料以及1000道Java面試題,都整理成了PDF,能夠關注公衆號 「悟空聊架構」 回覆悟空
領取優質資料。❞
「轉發->在看->點贊->收藏->評論!!!」 是對我最大的支持!
《Java併發必知必會》系列: