一口氣說出18種隊列(Queue),面試穩了!

在講《21張圖講解集合的線程不安全》那一篇,我留了一個彩蛋,就是Queue(隊列)尚未講,此次咱們重點來看看Java中的Queue家族,總共涉及到18種Queue。這篇恐怕是市面上最全最細 講解Queue的。java

本篇主要內容以下:程序員

本篇主要內容

幫你總結好的阻塞隊列:面試

18種Queue總結

1、Queue自我介紹

隊列原理圖

1.1 Queue自我介紹

hi,你們好,個人英文名叫Queue,中文名叫隊列,不管現實生活中仍是計算機的世界中,我都是一個很重要的角色哦~算法

我是一種數據結構,你們能夠把我想象成一個數組,元素從個人一頭進入、從另一頭出去,稱爲FIFO原則(先進先出原則)。編程

我還有兩個親兄弟:List(列表)、Set(集),他們都是Collection的兒子,我還有一個遠房親戚:Map(映射)。他們都是java.util包這個你們庭的成員哦~小程序

1.2 現實生活中的場景

  • 海底撈排號等位(先排號的優先進餐廳)
  • 郵政員寄送信件(信箱是隊列)

1.3 計算機世界中的場景

  • 消息隊列 RabbitMQ
  • UDP協議(接收端將消息存放在隊列中,從隊列中讀取數據)

2、高屋建瓴,縱覽全局

18種隊列分爲三大類: 接口、抽象類、普通類。數組

弄清楚下面的繼承實現關係對後面理解18種隊列有很大幫助。緩存

18個Queue的繼承實現關係圖

  • 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抽象類

注意:安全

  • Deque:全稱Double-Ended queue,表示雙端隊列。
  • 類實現接口,用implements
  • 接口繼承接口,用 extends
  • 類繼承類,用extends

3、萬物歸宗Queue接口

2.1 深刻理解Queue接口的本質

  • Queue接口是一種Collection,被設計用於處理以前臨時保存在某處的元素。markdown

  • 除了基本的Collection操做以外,隊列還提供了額外的插入、提取和檢查操做。每一種操做都有兩種形式:若是操做失敗,則拋出一個異常;若是操做失敗,則返回一個特殊值(null或false,取決因而什麼操做)。

  • 隊列一般是以FIFO(先進先出)的方式排序元素,可是這不是必須的。

  • 只有優先級隊列能夠根據提供的比較器對元素進行排序或者是採用正常的排序。不管怎麼排序,隊列的頭將經過調用remove()或poll()方法進行移除。在FIFO隊列種,全部新的元素被插入到隊尾。其餘種類的隊列可能使用不一樣的佈局來存放元素。

  • 每一個Queue必須指定排序屬性。

2.2 Queue接口的核心方法

總共有3組方法,每一組方法對應兩個方法。以下圖所示:

Queue的核心方法

動做 拋異常 返回特殊值
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方法的特殊返回值,以說明隊列不包含元素。

4、雙端可用Deque接口

4.1 深刻理解Deque接口的原理

雙端隊列Deque

(1)Deque概念: 支持兩端元素插入和移除的線性集合。名稱deque是雙端隊列的縮寫,一般發音爲deck。大多數實現Deque的類,對它們包含的元素的數量沒有固定的限制的,支持有界和無界。

(2)Deque方法說明:

Deque方法

**說明: **

  • 該列表包含包含訪問deque兩端元素的方法,提供了插入,移除和檢查元素的方法。

  • 這些方法種的每一種都存在兩種形式:若是操做失敗,則會拋出異常,另外一種方法返回一個特殊值(null或false,取決於具體操做)。

  • 插入操做的後一種形式專門設計用於容量限制的Deque實現,大多數實現中,插入操做不能失敗,因此能夠用插入操做的後一種形式。

  • Deque接口擴展了Queue接口,當使用deque做爲隊列時,做爲FIFO。元素將添加到deque的末尾,並從頭開始刪除。

  • 做爲FIFO時等價於Queue的方法以下表所示:

Deque等價於Queue的方法

好比Queue的add方法和Deque的addLast方法等價。

  • Deque也能夠用做LIFO(後進先出)棧,這個接口優於傳統的Stack類。看成爲棧使用時,元素被push到deque隊列的頭,而pop也是從隊列的頭pop出來。

  • Stack(棧)的方法正好等同於Deque的以下方法:

    Dque等價於Stack的方法

注意:peek方法不管是做爲棧仍是隊列,都是從隊列的檢測隊列的頭,返回最早加入的元素。好比第一次put 100,第二次put 200,則peek返回的是100。以下圖所示:

示例代碼

4.1 哪些類實現了Deque接口

  • LinkedList類
  • ArrayDeque類
  • ConcurrentLinkedDeque類
  • LinkedBlockingDeque類

4.2 哪些類繼承了Deque接口

  • BlockingDeque接口

5、隊列骨架AbstractQueue抽象類

5.1 深刻理解AbstractQueue抽象類

AbstractQueue是一個抽象類,繼承了Queue接口,提供了一些Queue操做的骨架實現。

AbstractQueue的方法

方法add、remove、element方法基於offer、poll和peek。也就是說若是不能正常操做,則拋出異常。咱們來看下AbstactQueue是怎麼作到的。

  • AbstractQueue的add方法
public boolean add(E e) {
    if (offer(e))
        return true;
    else
        throw new IllegalStateException("Queue full");
}
複製代碼
  • AbstractQueue的remove方法
public E remove() {
    E x = poll();
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
}
複製代碼
  • AbstractQueue的element方法
public E element() {
    E x = peek();
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
}
複製代碼

注意:

  • 若是繼承AbstractQueue抽象類則必須保證offer方法不容許null值插入。

5.2 哪些類繼承了AbstractQueue抽象類

  • ArrayBlockingQueue類、LinkendBlockingQueue類、LinkedBlockingDeque類、LinkedTransferQueue類、SynchronousQueue類、PriorityBlockQueue類、DelayQueue類繼承AbstractQueue抽象類
  • PriorityQueue類和ConcurrentLinkedQueue繼承AbstractQueue抽象類

6、阻塞緩衝BlockingQueue接口

6.1 宏觀來看BlockingQueue(阻塞隊列)

  • BlockQueue滿了,PUT操做被阻塞

阻塞隊列滿了的狀況

  • BlockQueue爲空,Take操做被阻塞

阻塞隊列爲空的狀況

(1)BlockingQueue(阻塞隊列)也是一種隊列,支持阻塞的插入和移除方法。

(3)阻塞的插入:當隊列滿時,隊列會阻塞插入元素的線程,直到隊列不滿。

(4)阻塞的移除:當隊列爲空,獲取元素的線程會等待隊列變爲非空。

(5)應用場景:生產者和消費者,生產者線程向隊列裏添加元素,消費者線程從隊列裏移除元素,阻塞隊列時獲取和存放元素的容器。

(6)爲何要用阻塞隊列:生產者生產和消費者消費的速率不同,須要用隊列來解決速率差問題,當隊列滿了或空的時候,則須要阻塞生產或消費動做來解決隊列滿或空的問題。

6.2 案例解析

線程A往阻塞隊列(Blocking Queue)中添加元素,而線程B從阻塞隊列中移除元素。

  • 當阻塞隊列爲空的時候 (一個元素都沒有),則從隊列中獲取元素的操做將會被阻塞。
    • 生活中的案例:去海底撈吃火鍋的時候,早上8點沒人來吃火鍋,因此須要等客人過來。
    • 翻譯成線程:如今沒有元素須要添加,並且阻塞隊列爲空,因此線程B不須要從隊列中拿元素出來,因此線程B獲取元素的操做被阻塞了。
  • 當阻塞隊列滿了的時候 (全部位置都放有元素),則從隊列中添加元素的操做將會被阻塞。
    • 生活中的案例:去海底撈吃火鍋的時候,人太多了,須要排號,等其餘桌空出來了才能進去。
    • 翻譯成線程:線程A往阻塞隊列中添加元素,將隊列填滿了,線程B如今正在忙,沒法拿出隊列中的元素,因此阻塞隊列沒有地方再放元素了,這個時候線程A添加元素的操做就被阻塞了

6.3 操刀BlockingQueue接口

BlockingQueue接口的10個核心方法:

繼承的方法

10個核心方法總結以下:

BlockingQueue接口的10個核心方法

有三大類操做:插入、移除、檢查。

  • 插入有四種方法: add、offer、put、offer超時版。
    • add方法特別之處用於添加失敗時拋出異常,共有四種異常:
      • IllegalStateException - 隊列滿了
      • ClassCastException - 添加的元素類型不匹配
      • NullPointerException - 添加的元素爲null
      • IllegalArgumentException - 添加的元素某些屬性不匹配
    • offer方法特別之處用於添加失敗時只返回false
    • put方法特別之處用於當阻塞隊列滿時,生產者若是往隊列裏put元素,則隊列會一直阻塞生產者線程,直到隊列可用或者響應中斷退出
    • offer超時方法特別之處用於當阻塞隊列滿時,生產者若是往隊列裏面插入元素,隊列會阻塞生產者線程一段時間,若是超過了指定時間,生產者線程會退出,並返回false。
  • 移除有四種方法: remove、poll、take、poll超時版
    • remove方法特別之處用於移除失敗時拋出異常
      • NoSuchElementException - 若是這個隊列是空的
    • poll方法特別之處用於移除失敗時返回null
    • take方法特別之處用於當阻塞隊列爲空時,消費者線程若是從隊列裏面移除元素,則隊列會一直阻塞消費者線程,直到隊列不爲空
    • poll超時方法特別之處用於當阻塞隊列空時,消費者若是從隊列裏面刪除元素,則隊列會一直阻塞消費者線程,若是超過了指定時間,消費者線程會退出,並返回null
  • 檢查有兩種方法: element、peek
    • element方法用於檢測頭部元素的存在性,若是隊列爲空,則拋出異常,不然返回頭部元素。
    • peek方法用於檢測頭部元素的存在性,若是隊列爲空,返回特殊值null,不然返回頭部的元素。

6.4 BlockingQueue經過什麼來阻塞插入和移除的?

  • 當往隊列裏插入一個元素時,若是隊列不可用,那麼阻塞生產者主要經過LockSupport. park(this)來實現。
  • park這個方法會阻塞當前線程,只有如下4種狀況中的一種發生時,該方法纔會返回。
    • 與park對應的unpark執行或已經執行時。「已經執行」是指unpark先執行,而後再執行park的狀況。
    • 線程被中斷時。
    • 等待完time參數指定的毫秒數時。
    • 異常現象發生時,這個異常現象沒有任何緣由。

6.5 哪些類繼承了BlockingQueue接口?

  • BlockingDeque接口 - 雙端阻塞隊列
  • TransferQueue接口 - 傳輸隊列

6.6 哪些類實現了BlockingQueue接口?

  • ArrayBlockingQueue類 - 由數組構成的有界阻塞隊列
  • LinkedBlockingQueue類 - 由鏈表構成的有界阻塞隊列,界限默認大小爲Integer.MAX_Value(2^31-1),值很是大,至關於無界。
  • LinkedBlockingDeque類 - 由鏈表構成的雙向阻塞隊列
  • LinkedTransferQueue類 - 由鏈表構成的無界阻塞隊列
  • SynchronousQueue類 - 不存儲元素的阻塞隊列,只有一個元素進行數據傳遞。
  • LinkedTransferQueue類 - 由鏈表構成的無界阻塞TransferQueue隊列
  • DelayQueue類 - 使用優先級隊列實現的延遲無界阻塞隊列

6.6 BlockingQueue接口繼承了哪些接口

  • BlockingQueue接口繼承了Queue接口,可做爲隊列使用

7、雙端阻塞BlockingDeque接口

7.1 從原理圖上理解BlockDeque

  • BlockQueue滿了,兩端的Take操做被阻塞

BlockingDeque滿了

  • BlockQueue爲空,兩端的Take操做被阻塞

BlockQueue爲空

7.2 BlockingDeque接口方法

是阻塞隊列BlockingQueue和雙向隊列Deque接口的結合。有以下方法:

BlockingDeque接口方法

示例:

嘗試執行如下方法:

LinkedBlockingDeque queue = new LinkedBlockingDeque();
queue.addFirst("test1");
queue.addFirst(300);
queue.addLast("400");
複製代碼

最後隊列中的元素順序以下:

300, test1, 400。

先添加了test1放到隊列的頭部,而後在頭部的前面放入300,因此300在最前面,成爲頭部,而後將400放入隊列的尾部,因此最後的結果是300, test1, 400。

隊列種的元素

7.3 BlockDeque和BlockQueue的對等方法

mark

7.4 BlockingDeque的特色

  • 線程安全。
  • 不容許使用null元素。
  • 無界和有界均可以。

7.5 BlockingDeque接口繼承了哪些接口?

  • Queue接口,具備隊列的功能
  • Deque接口,具備雙端隊列的功能
  • BlockingQueue接口,可做爲阻塞隊列使用

7.6 哪些類實現了BlockDeque接口?

  • LinkedBlockingDeque

8、使命必達TransferQueue接口

8.1 Transfer怎麼理解?

若是有消費者正在獲取元素,則將隊列中的元素傳遞給消費者。若是沒有消費者,則等待消費者消費。我把它稱做使命必達隊列,必須將任務完成才能返回。

8.2 生活中的案例

  • **針對TransferQueue的transfer方法 **
    • 圓通快遞員要將小明的2個快遞送貨到門,韻達快遞員也想將小明的2個快遞送貨到門。小明一次只能拿一個,快遞員必須等小明拿了一個後,才能繼續給第二個。
  • 針對TransferQueue的tryTransfer方法
    • 圓通快遞員要將小明的2個快遞送貨到門,韻達快遞員也想將小明的2個快遞送貨到門。發現小明不在家,就把快遞直接放到菜鳥驛站了。
  • 針對TransferQueue的tryTransfer超時方法
    • 圓通快遞員要將小明的2個快遞送貨到門,韻達快遞員也想將小明的2個快遞送貨到門。發現小明不在家,因而先等了5分鐘,發現小明尚未回來,就把快遞直接放到菜鳥驛站了。

8.3 TransferQueue的原理解析

  • transfer(E e)

    原理以下圖所示:

    transfer方法的原理

    • 原理圖解釋:生產者線程Producer Thread嘗試將元素B傳給消費者線程,若是沒有消費者線程,則將元素B放到尾節點。而且生產者線程等待元素B被消費。當元素B被消費後,生產者線程返回。
    • 若是當前有消費者正在等待接收元素(消費者經過take方法或超時限制的poll方法時),transfer方法能夠把生產者傳入的元素馬上transfer(傳輸)給消費者。
    • 若是沒有消費者等待接收元素,transfer方法會將元素放在隊列的tail(尾)節點,並等到該元素被消費者消費了才返回。
  • tryTransfer(E e)

    • 試探生產者傳入的元素是否能直接傳給消費者。
    • 若是沒有消費者等待接收元素,則返回false。
    • 和transfer方法的區別是,不管消費者是否接收,方法當即返回。
  • tryTransfer(E e, long timeout, TimeUnit unit)

    • 帶有時間限制的tryTransfer方法。
    • 試圖把生產者傳入的元素直接傳給消費者。
    • 若是沒有消費者消費該元素則等待指定的時間再返回。
    • 若是超時了尚未消費元素,則返回false。
    • 若是在超時時間內消費了元素,則返回true。
  • getWaitingConsumerCount()

    • 獲取經過BlockingQueue.take()方法或超時限制poll方法等待接受元素的消費者數量。近似值。
    • 返回等待接收元素的消費者數量。
  • hasWaitingConsumer()

    • 獲取是否有經過BlockingQueue.tabke()方法或超時限制poll方法等待接受元素的消費者。
    • 返回true則表示至少有一個等待消費者。

8.3 TransferQueue接口繼承了哪些接口?

  • BlockingQueue接口,可做爲阻塞隊列使用
  • Queue接口,可做爲隊列使用

8.4 哪些類實現了TransferQueue接口?

  • LinkedTranferQueue接口

9、優先由你PriorityQueue類

9.1 理解PriorityQueue類

  • 本應該按照升序排序

本應該按照升序排序

  • 按照倒敘排序

按照自定義優先級排序

  • PriorityQueue是一個支持優先級的無界阻塞隊列。

  • 默認天然順序升序排序。

  • 能夠經過構造參數Comparator來對元素進行排序。

public PriorityQueue(Comparator<? super E> comparator) {
     this(DEFAULT_INITIAL_CAPACITY, comparator);
}
複製代碼
  • 自定義實現comapreTo()方法來指定元素排序規則。
public Comparator<? super E> comparator() {
    return comparator;
}
複製代碼
  • 不容許插入null元素。
  • 實現PriorityQueue接口的類,不保證線程安全,除非是PriorityBlockingQueue。
  • PriorityQueue的迭代器不能保證以任何特定順序遍歷元素,若是須要有序遍歷,請考慮使用Arrays.sort(pq.toArray)
  • 進列(offeradd)和出列( pollremove())的時間複雜度O(log(n))。
  • remove(Object) 和 contains(Object)的算法時間複雜度O(n)。
  • peek、element、size的算法時間複雜度爲O(1)。

9.2 PriorityQueue類繼承了哪些類?

  • AbstractQueue抽象類,具備隊列的功能

9.2 PriorityQueue類實現了哪些接口?

  • Queue接口,可做爲隊列使用。

10、雙向鏈表LinkedList類

10.1 LinkedList的結構

  • LinkedList實現了List和Deque接口,因此是一種雙鏈表結構,能夠看成堆棧、隊列、雙向隊列使用。
  • 一個雙向列表的每個元素都有三個整數值:元素、向後的節點連接、向前的節點連接

LinkedList的結構

咱們來看下節點類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;
    }
}
複製代碼

10.2 與ArrayList的區別

  • 1.LinkedList的增長和刪除效率相對較高,而查找和修改的效率相對較低。

  • 2.如下狀況建議使用ArrayList

    • 頻繁訪問列表中的一個元素。
    • 只在列表的首尾添加元素。
  • 3.如下狀況建議使用LinkedList

    • 頻繁地在列表開頭、中間、末尾添加和刪除元素。
    • 須要經過循環迭代來訪問列表中的元素。

10.3 LinkedList不是線程安全的

LinkedList不是線程安全的,因此可使用以下方式保證線程安全。

List list = Collections.synchronizedList(new LinkedList<>());
複製代碼

10.4 LinkedList的家庭成員關係

  • LinkedList 繼承了 AbstractSequentialList 類。

  • LinkedList 實現了 Queue 接口,可做爲隊列使用。

  • LinkedList 繼承了 AbstractQueue抽象類,具備隊列的功能。

  • LinkedList 實現了 List 接口,可進行列表的相關操做。

  • LinkedList 實現了 Deque 接口,可做爲雙向隊列使用。

  • LinkedList 實現了 Cloneable 接口,可實現克隆。

  • LinkedList 實現了 java.io.Serializable 接口,便可支持序列化,能經過序列化去傳輸。

11、併發安全ConcurrentLinkedQueue類

11.1 理解ConcurrentLinkedQueue

ConcurrentLinkedQueue原理

  • ConcurrentLinked是由鏈表結構組成的線程安全的先進先出無界隊列。
  • 當多線程要共享訪問集合時,ConcurrentLinkedQueue是一個比較好的選擇。
  • 不容許插入null元素
  • 支持非阻塞地訪問併發安全的隊列,不會拋出ConcurrentModifiationException異常。
  • size方法不是準確的,由於在統計集合的時候,隊列可能正在添加元素,致使統計不許。
  • 批量操做addAll、removeAll、retainAll、containsAll、equals和toArray不保證原子性(操做不可分割)
  • 添加元素happen-before其餘線程移除元素。
  • 用法以下:
ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();
BuildingBlockWithName buildingBlock = new BuildingBlockWithName("三角形", "A");
concurrentLinkedQueue.add(buildingBlock);
複製代碼

11.2 ConcurrentLinkedQueue類繼承了哪些類?

  • AbstractQueue抽象類,具備隊列的功能

11.3 ConcurrentLinkedQueue類實現了哪些接口?

  • Queue接口,可做爲隊列使用

12、雙向數組ArrayDeque類

ArrayDeque原理圖

12.1 理解ArrayDeque

  • 由數組組成的雙端隊列。
  • 沒有容量限制,根據須要擴容。
  • 不是線程安全的。
  • 禁止插入null元素。
  • 當用做棧時,比棧速度快,當用做隊列時,速度比LinkList快。
  • 大部分方法的算法時間複雜度爲O(1)。
  • remove、removeFirstOccurrence、removeLastOccurrence、contains、remove 和批量操做的算法時間複雜度O(n)

12.2 使用方法

建立一個ArrayDeque,往arrayDeque隊尾添加元素。

ArrayDeque arrayDeque = new ArrayDeque();
for (int i = 0; i < 50; i++) {
    arrayDeque.add(buildingBlock); // add方法等價於addLast方法
}
複製代碼

12.3 ArrayDeque實現了哪些接口

  • Deque接口 - 可用於雙端隊列

十3、雙向併發ConcurrentLinkedDeque類

13.1 理解ConcurrentLinkedDeque類

ConcurrentLinkedDeque原理圖

  • 由鏈表結構組成的雙向無界阻塞隊列
  • 插入、刪除和訪問操做能夠併發進行,線程安全的類
  • 不容許插入null元素
  • 在併發場景下,計算隊列的大小是不許確的,由於計算時,可能有元素加入隊列。
  • 批量操做addAll、removeAll、retainAll、containsAll、equals和toArray不保證原子性(操做不可分割)

13.2 ConcurrentLinkedDeque使用示例

建立兩個積木:三角形、四邊形,而後添加到隊列:

BuildingBlockWithName buildingBlock1 = new BuildingBlockWithName("三角形", "A");
BuildingBlockWithName buildingBlock2 = new BuildingBlockWithName("四邊形", "B");
ConcurrentLinkedDeque concurrentLinkedDeque = new ConcurrentLinkedDeque();
concurrentLinkedDeque.addFirst(buildingBlock1);
concurrentLinkedDeque.addLast(buildingBlock2);
//結果:順序:三角形、四邊形
複製代碼

13.3 ConcurrentLinkedDeque實現了哪些接口

  • Deque接口 - 可用於雙端隊列

十4、數組阻塞ArrayBlockingQueue類

14.1 理解ArrayBlockingQueue

ArrayBlockingQueuey原理圖

  • ArrayBlockingQueue是一個用數組實現的有界阻塞隊列。
  • 隊列慢時插入操做被阻塞,隊列空時,移除操做被阻塞。
  • 按照先進先出(FIFO)原則對元素進行排序。
  • 默認不保證線程公平的訪問隊列。
  • 公平訪問隊列:按照阻塞的前後順序訪問隊列,即先阻塞的線程先訪問隊列。
  • 非公平性是對先等待的線程是非公平的,當隊列可用時,阻塞的線程均可以爭奪訪問隊列的資格。有可能先阻塞的線程最後才訪問訪問隊列。
  • 公平性會下降吞吐量。

14.2 ArrayBlockingQueue使用示例

建立兩個積木:三角形、四邊形,而後添加到隊列:

BuildingBlockWithName buildingBlock1 = new BuildingBlockWithName("三角形", "A");
BuildingBlockWithName buildingBlock2 = new BuildingBlockWithName("四邊形", "B");
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(100, true);
arrayBlockingQueue.add(buildingBlock1);
arrayBlockingQueue.add(buildingBlock2);
複製代碼

14.3 ArrayBlockQueue實現了哪些接口

  • Deque接口 - 可用於雙端隊列

十5、鏈表阻塞LinkedBlockingQueue類

15.1 理解LinkedBlockingQueue

LinkedBlockingQueue原理

  • LinkedBlockingQueue具備單鏈表和有界阻塞隊列的功能。
  • 隊列慢時插入操做被阻塞,隊列空時,移除操做被阻塞。
  • 默認和最大長度爲Integer.MAX_VALUE,至關於無界(值很是大:2^31-1)。

15.2 LinkedBlockingQueue使用示例

LinkedList linkedList1 = new LinkedList();
linkedList1.add("A");
linkedList1.add("B");
linkedList1.add("C");
複製代碼

15.3 LinkedBlockingQueue的應用場景

  • 吞吐量一般要高於ArrayBlockingQueue。建立線程池時,參數runnableTaskQueue(任務隊列),用於保存等待執行的任務的阻塞隊列能夠選擇LinkedBlockingQueue。靜態工廠方法Executors.newFixedThreadPool()使用了這個隊列。

15.4 LinkedBlockingQueue實現了哪些接口

  • LinkedBlockingQueue繼承了 BlockingQueue類,可做爲阻塞隊列使用
  • LinkedBlockingQueue繼承了 AbstractQueue抽象類,具備隊列的功能。
  • LinkedBlockingQueue實現了 java.io.Serializable 接口,便可支持序列化,能經過序列化去傳輸。

十6、雙向阻塞LinkedBlockingDeque類

16.1 理解LinkedBlockingDeque類

LinkedBlockingDeque原理圖

  • 由鏈LinkedBlockingDeque = 阻塞隊列+鏈表+雙端訪問
  • 線程安全。
  • 多線程同時入隊時,因多了一端訪問入口,因此減小了一半的競爭。
  • 默認容量大小爲Integer.MAX_VALUE。可指定容量大小。

16.2 LinkedBlockingDeque的應用場景

LinkedBlockingDeque能夠用在「工做竊取「模式中。

工做竊取算法:某個線程比較空閒,從其餘線程的工做隊列中的隊尾竊取任務來幫忙執行。

16.3 LinkedBlockingDeque實現了哪些接口

  • LinkedBlockingDeque繼承了 BlockingDeque類,可做爲阻塞隊列使用
  • LinkedBlockingDeque繼承了 AbstractQueue抽象類,具備隊列的功能。
  • LinkedBlockingDeque實現了 java.io.Serializable 接口,便可支持序列化,能經過序列化去傳輸。

十7、鏈表阻塞LinkedTransferQueue類

17.1 理解LinkedTransferQueue類

LinkedTransferQueue原理圖

LinkedTransferQueue = 阻塞隊列+鏈表結構+TransferQueue

以前咱們講「使命必達TransferQueue接口時已經介紹過了TransferQueue接口 ,因此LinkedTransferQueue接口跟它類似,只是加入了阻塞插入和移除的功能,以及結構是鏈表結構。

以前的TransferQueue也講到了3個案例來講明TransferQueue的原理,你們能夠回看TransferQueue。

17.2 LinkedTransferQueue接口比其餘阻塞隊列多了5個方法

  • transfer(E e)
  • tryTransfer(E e)
  • tryTransfer(E e, long timeout, TimeUnit unit)
  • getWaitingConsumerCount()
  • hasWaitingConsumer()

17.3 LinkedTransferQueue代碼示例

  • 建立一個LinkedTransferQueue,生產者1 依次往隊列中添加 A、B、C

生產者1 依次往隊列中添加 A、B、C

  • 生產者2 依次往隊列中添加 D、E、F

生產者2 依次往隊列中添加 D、E、F

  • 消費者依次從隊列首部開始消費元素,每次消費前,先sleep 2s,來演示transfer方法是否進行了等待。

消費者消費元素

  • 運行結果
生產者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排在隊列的首部。

隊列裏面的元素

17.4 LinkedTransferQueue實現了哪些接口

  • LinkedBlockingDeque繼承了 BlockingQeque類,可做爲阻塞隊列使用
  • LinkedBlockingDeque繼承了 AbstractQueue抽象類,具備隊列的功能。

十8、傳球好手SynchronousQueue類

18.1 理解SynchronousQueue類

SynchronousQueue原理圖

  • 我稱SynchronousQueue爲」傳球好手「。想象一下這個場景:小明抱着一個籃球想傳給小花,若是小花沒有將球拿走,則小明是不能再拿其餘球的。

  • SynchronousQueue負責把生產者產生的數據傳遞給消費者線程。

  • SynchronousQueue自己不存儲數據,調用了put方法後,隊列裏面也是空的。

  • 每個put操做必須等待一個take操做完成,不然不能添加元素。

  • 適合傳遞性場景。

  • 性能高於ArrayBlockingQueue和LinkedBlockingQueue。

18.2 SynchronousQueue示例

咱們建立了兩個線程,一個線程用於生產,一個線程用於消費

  • 生產的線程依次put A、B、C三個值

生產的線程依次put A、B、C三個值

  • 消費線程使用take來消費阻塞隊列中的內容,每次消費前,等待5秒

消費線程每隔5s調用take方法

  • 運行結果
t1     put A 
t2     take A 

5秒後...

t1     put B 
t2     take B 

5秒後...

t1     put C 
t2     take C 
複製代碼

小結:說明生產線程執行put第一個元素"A" 操做後,須要等待消費者線程take完「A」後,才能繼續往下執行代碼。

18.3 SynchronousQueue應用場景

  • 吞吐量一般要高於LinkedBlockingQueue。建立線程池時,參數runnableTaskQueue(任務隊列),用於保存等待執行的任務的阻塞隊列能夠選擇SynchronousQueue。靜態工廠方法Executors.newCachedThreadPool()使用了這個隊列

18.4 SynchronousQueue和LinkedTransferQueue的區別

  • SynchronousQueue 不存儲元素,而LinkedTransferQueue存儲元素。
  • SynchronousQueue 隊列裏面沒有元素,而LinkedTransferQueue能夠有多個存儲在隊列等待傳輸。
  • LinkedTransferQueue還支持若傳輸不了,則丟到隊列裏面去。
  • LinkedTransferQueue還支持若超過必定時間傳輸不了,則丟到隊列裏面去。

十9、優先級阻塞PriorityBlockingQueue類

19.1 理解PriorityBlockQueue類

PriorityBlockQueue的原理圖

  • PriorityBlockQueue = PriorityQueue + BlockingQueue
  • 以前咱們也講到了PriorityQueue的原理,支持對元素排序。
  • 元素默認天然排序。
  • 能夠自定義CompareTo()方法來指定元素排序規則。
  • 能夠經過構造函數構造參數Comparator來對元素進行排序。

19.2 PriorityBlockQueue實現了哪些接口

  • LinkedBlockingQueue繼承了 BlockingQueue接口,可做爲阻塞隊列使用
  • LinkedBlockingQueue繼承了 AbstractQueue抽象類,具備隊列的功能。
  • LinkedBlockingQueue實現了 java.io.Serializable 接口,便可支持序列化,能經過序列化去傳輸。

二10、延時阻塞DelayQueue類

20.1 理解DelayQueue

DelayQueue原理圖

  • DelayQueue = Delayed + BlockingQueue。隊列中的元素必須實現Delayed接口。
public class DelayQueue<E extends Delayed> extends AbstractQueue<E> implements BlockingQueue<E> {
複製代碼
  • 在建立元素時,能夠指定多久能夠從隊列中獲取到當前元素。只有在延時期滿才能從隊列中獲取到當前元素。

20.2 源碼解析

  • 添加元素時,指定延時多久能夠從隊列中獲取元素
public boolean offer(E e, long timeout, TimeUnit unit) {
    return offer(e);
}
複製代碼
  • 獲取元素的方法poll須要等待延時時間過了才能獲取到元素
if (first == null || first.getDelay(NANOSECONDS) > 0)
    return null;
else
    return q.poll();
複製代碼

poll方法

20.3 應用場景

  • 緩存系統的設計:能夠用DelayQueue保存緩存元素的有效期。而後用一個線程循環的查詢DelayQueue隊列,一旦能從DelayQueue中獲取元素時,表示緩存有效期到了。

  • 定時任務調度:使用DelayQueue隊列保存當天將會執行的任務和執行時間,一旦從DelayQueue中獲取到任務就開始執行。好比Java中的TimerQueue就是使用DelayQueue實現的。

20.4 DelayQueue實現了哪些接口

  • DelayQueue實現了 BlockingQueue接口,可做爲阻塞隊列使用

這一篇花了不少心思在上面,看官方英文文檔、畫原理圖、寫demo代碼,排版。這恐怕是市面上最全最細講解Queue的。

mark

你好,我是悟空哥「7年項目開發經驗,全棧工程師,開發組長,超喜歡圖解編程底層原理」。正在編寫兩本PDF,分別是 一、Spring Cloud實戰項目(佳必過),二、Java併發必知必會。我還手寫了2個小程序,Java刷題小程序,PMP刷題小程序,點擊個人公衆號菜單打開!另外有111本架構師資料以及1000道Java面試題,都整理成了PDF,能夠關注公衆號 「悟空聊架構」 回覆 悟空 領取優質資料。

二維碼

「轉發->在看->點贊->收藏->評論!!!」 是對我最大的支持!

《Java併發必知必會》系列:

1.反制面試官 | 14張原理圖 | 不再怕被問 volatile!

2.程序員深夜慘遭老婆鄙視,緣由竟是CAS原理太簡單?

3.用積木講解ABA原理 | 老婆竟然又聽懂了!

4.全網最細 | 21張圖帶你領略集合的線程不安全

5.5000字 | 24張圖帶你完全理解Java中的21種鎖

相關文章
相關標籤/搜索