【java多線程】隊列系統之說說隊列Queue

轉載:http://benjaminwhx.com/2018/05/05/%E8%AF%B4%E8%AF%B4%E9%98%9F%E5%88%97Queue/前端

一、簡介

Queue(隊列):一種特殊的線性表,它只容許在表的前端(front)進行刪除操做,只容許在表的後端(rear)進行插入操做。進行插入操做的端稱爲隊尾,進行刪除操做的端稱爲隊頭。後端

每一個元素老是從隊列的rear端進入隊列,而後等待該元素以前的全部元素出隊以後,當前元素才能出對,遵循先進先出(FIFO)原則。數組

下面是Queue類的繼承關係圖:緩存

圖中咱們能夠看到,最上層是Collection接口,Queue知足集合類的全部方法:併發

  • add(E e):增長元素;
  • remove(Object o):刪除元素;
  • clear():清除集合中全部元素;
  • size():集合元素的大小;
  • isEmpty():集合是否沒有元素;
  • contains(Object o):集合是否包含元素o。

二、隊列

2.一、Queue

Queue:隊列的上層接口,提供了插入、刪除、獲取元素這3種類型的方法,並且對每一種類型都提供了兩種方式,先來看看插入方法:app

  • add(E e):插入元素到隊尾,插入成功返回true,沒有可用空間拋出異常 IllegalStateException。
  • offer(E e): 插入元素到隊尾,插入成功返回true,不然返回false。

add和offer做爲插入方法的惟一不一樣就在於隊列滿了以後的處理方式。add拋出異常,而offer返回false。ide

再來看看刪除和獲取元素方法(和插入方法相似):函數

  • remove():獲取並移除隊首的元素,該方法和poll方法的不一樣之處在於,若是隊列爲空該方法會拋出異常,而poll不會。
  • poll():獲取並移除隊首的元素,若是隊列爲空,返回null。
  • element():獲取隊列首的元素,該方法和peek方法的不一樣之處在於,若是隊列爲空該方法會拋出異常,而peek不會。
  • peek():獲取隊列首的元素,若是隊列爲空,返回null。

若是隊列是空,remove和element方法會拋出異常,而poll和peek返回null。this

固然,Queue只是單向隊列,爲了提供更強大的功能,JDK在1.6的時候新增了一個雙向隊列Deque,用來實現更靈活的隊列操做。spa

2.二、Deque

Deque在Queue的基礎上,增長了如下幾個方法:

  • addFirst(E e):在前端插入元素,異常處理和add同樣;
  • addLast(E e):在後端插入元素,和add同樣的效果;
  • offerFirst(E e):在前端插入元素,異常處理和offer同樣;
  • offerLast(E e):在後端插入元素,和offer同樣的效果;
  • removeFirst():移除前端的一個元素,異常處理和remove同樣;
  • removeLast():移除後端的一個元素,和remove同樣的效果;
  • pollFirst():移除前端的一個元素,和poll同樣的效果;
  • pollLast():移除後端的一個元素,異常處理和poll同樣;
  • getFirst():獲取前端的一個元素,和element同樣的效果;
  • getLast():獲取後端的一個元素,異常處理和element同樣;
  • peekFirst():獲取前端的一個元素,和peek同樣的效果;
  • peekLast():獲取後端的一個元素,異常處理和peek同樣;
  • removeFirstOccurrence(Object o):從前端開始移除第一個是o的元素;
  • removeLastOccurrence(Object o):從後端開始移除第一個是o的元素;
  • push(E e):和addFirst同樣的效果;
  • pop():和removeFirst同樣的效果。

能夠發現,其實不少方法的效果都是同樣的,只不過名字不一樣。好比Deque爲了實現Stack的語義,定義了push和pop兩個方法。

三、阻塞隊列

3.一、BlockingQueue

BlockingQueue(阻塞隊列),在Queue的基礎上實現了阻塞等待的功能。它是JDK 1.5中加入的接口,它是指這樣的一個隊列:當生產者向隊列添加元素但隊列已滿時,生產者會被阻塞;當消費者從隊列移除元素但隊列爲空時,消費者會被阻塞。

先給出BlockingQueue新增的方法:

  • put(E e):向隊尾插入元素。若是隊列滿了,阻塞等待,直到被中斷爲止。
  • boolean offer(E e, long timeout, TimeUnit unit):向隊尾插入元素。若是隊列滿了,阻塞等待timeout個時長,若是到了超時時間尚未空間,拋棄該元素。
  • take():獲取並移除隊首的元素。若是隊列爲空,阻塞等待,直到被中斷爲止。
  • poll(long timeout, TimeUnit unit):獲取並移除隊首的元素。若是隊列爲空,阻塞等待timeout個時長,若是到了超時時間尚未元素,則返回null。
  • remainingCapacity():返回在無阻塞的理想狀況下(不存在內存或資源約束)此隊列能接受的元素數量,若是該隊列是無界隊列,返回Integer.MAX_VALUE。
  • drainTo(Collection<? super E> c):移除此隊列中全部可用的元素,並將它們添加到給定 collection 中。
  • drainTo(Collection<? super E> c, int maxElements):最多今後隊列中移除給定數量的可用元素,並將這些元素添加到給定 collection 中。

BlockingQueue最重要的也就是關於阻塞等待的幾個方法,而這幾個方法正好能夠用來實現生產-消費的模型

從圖中咱們能夠知道實現了BlockingQueue的類有如下幾個:

  • ArrayBlockingQueue:一個由數組結構組成的有界阻塞隊列。
  • LinkedBlockingQueue:一個由鏈表結構組成的有界阻塞隊列。
  • PriorityBlockingQueue:一個支持優先級排序的無界阻塞隊列。
  • SynchronousQueue:一個不存儲元素的阻塞隊列。
  • DelayQueue:一個使用優先級隊列實現的無界阻塞隊列。

ArrayBlockingQueue

ArrayBlockingQueue是一個用數組實現的有界阻塞隊列。此隊列按照先進先出(FIFO)的原則對元素進行排序。默認狀況下不保證訪問者公平的訪問隊列,所謂公平訪問隊列是指阻塞的全部生產者線程或消費者線程,當隊列可用時,能夠按照阻塞的前後順序訪問隊列,即先阻塞的生產者線程,能夠先往隊列裏插入元素,先阻塞的消費者線程,能夠先從隊列裏獲取元素。一般狀況下爲了保證公平性會下降吞吐量。咱們可使用如下代碼建立一個公平的阻塞隊列:

ArrayBlockingQueue fairQueue = new  ArrayBlockingQueue(1000, true);
View Code

訪問者的公平性是使用可重入鎖實現的,代碼以下:

public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    this.items = new Object[capacity];
    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}
View Code

 

LinkedBlockingQueue

LinkedBlockingQueue是一個用鏈表實現的有界阻塞隊列。此隊列的默認和最大長度爲Integer.MAX_VALUE。此隊列按照先進先出的原則對元素進行排序。

PriorityBlockingQueue

PriorityBlockingQueue是一個支持優先級的無界隊列。默認狀況下元素採起天然順序排列,也能夠經過比較器comparator來指定元素的排序規則。元素按照升序排列。

SynchronousQueue

SynchronousQueue是一個不存儲元素的阻塞隊列。每個put操做必須等待一個take操做,不然不能繼續添加元素。SynchronousQueue能夠當作是一個傳球手,負責把生產者線程處理的數據直接傳遞給消費者線程。隊列自己並不存儲任何元素,很是適合於傳遞性場景,好比在一個線程中使用的數據,傳遞給另一個線程使用,SynchronousQueue的吞吐量高於LinkedBlockingQueue 和 ArrayBlockingQueue。

DelayQueue

DelayQueue是一個支持延時獲取元素的無界阻塞隊列。隊列使用PriorityQueue來實現。隊列中的元素必須實現Delayed接口,在建立元素時能夠指定多久才能從隊列中獲取當前元素。只有在延遲期滿時才能從隊列中提取元素。咱們能夠將DelayQueue運用在如下應用場景:

  • 緩存系統的設計:能夠用DelayQueue保存緩存元素的有效期,使用一個線程循環查詢DelayQueue,一旦能從DelayQueue中獲取元素時,表示緩存有效期到了。
  • 定時任務調度。使用DelayQueue保存當天將會執行的任務和執行時間,一旦從DelayQueue中獲取到任務就開始執行,從好比TimerQueue就是使用DelayQueue實現的。

3.二、BlockingDeque

BlockingDeque(阻塞雙端隊列)在Deque的基礎上實現了雙端阻塞等待的功能。和第2節說的相似,BlockingDeque也提供了雙端隊列該有的阻塞等待方法:

  • putFirst(E e):在隊首插入元素,若是隊列滿了,阻塞等待,直到被中斷爲止。
  • putLast(E e):在隊尾插入元素,若是隊列滿了,阻塞等待,直到被中斷爲止。
  • offerFirst(E e, long timeout, TimeUnit unit):向隊首插入元素。若是隊列滿了,阻塞等待timeout個時長,若是到了超時時間尚未空間,拋棄該元素。
  • offerLast(E e, long timeout, TimeUnit unit):向隊尾插入元素。若是隊列滿了,阻塞等待timeout個時長,若是到了超時時間尚未空間,拋棄該元素。
  • takeFirst():獲取並移除隊首的元素。若是隊列爲空,阻塞等待,直到被中斷爲止。
  • takeLast():獲取並移除隊尾的元素。若是隊列爲空,阻塞等待,直到被中斷爲止。
  • pollFirst(long timeout, TimeUnit unit):獲取並移除隊首的元素。若是隊列爲空,阻塞等待timeout個時長,若是到了超時時間尚未元素,則返回null。
  • pollLast(long timeout, TimeUnit unit):獲取並移除隊尾的元素。若是隊列爲空,阻塞等待timeout個時長,若是到了超時時間尚未元素,則返回null。
  • removeFirstOccurrence(Object o):從隊首開始移除第一個和o相等的元素。
  • removeLastOccurrence(Object o):從隊尾開始移除第一個和o相等的元素。

從圖中咱們能夠知道實現了BlockingDeque的類有:

  • LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列。

3.三、TransferQueue

TransferQueue是JDK 1.7對於併發類庫新增長的一個接口,它擴展自BlockingQueue,因此天然保持着阻塞隊列的全部特性。

有人這樣評價它:TransferQueue是是ConcurrentLinkedQueue、SynchronousQueue (公平模式下)、無界的LinkedBlockingQueues等的超集。

TransferQueue對比與BlockingQueue更強大的一點是,生產者會一直阻塞直到所添加到隊列的元素被某一個消費者所消費(不只僅是添加到隊列裏就完事)。新添加的transfer方法用來實現這種約束。顧名思義,阻塞就是發生在元素從一個線程transfer到另外一個線程的過程當中,它有效地實現了元素在線程之間的傳遞(以創建Java內存模型中的happens-before關係的方式)。

咱們來看看該接口提供的標準方法:

  • tryTransfer(E e):若當前存在一個正在等待獲取的消費者線程(使用take()或者poll()函數),使用該方法會即刻轉移/傳輸對象元素e並當即返回true;若不存在,則返回false,而且不進入隊列。這是一個不阻塞的操做。
  • transfer(E e):若當前存在一個正在等待獲取的消費者線程,即馬上移交之;不然,會插入當前元素e到隊列尾部,而且等待進入阻塞狀態,到有消費者線程取走該元素。
  • tryTransfer(E e, long timeout, TimeUnit unit):若當前存在一個正在等待獲取的消費者線程,會當即傳輸給它;不然將插入元素e到隊列尾部,而且等待被消費者線程獲取消費掉;若在指定的時間內元素e沒法被消費者線程獲取,則返回false,同時該元素被移除。
  • hasWaitingConsumer():判斷是否存在消費者線程。
  • getWaitingConsumerCount():獲取全部等待獲取元素的消費線程數量。

其實transfer方法在SynchronousQueue的實現中就已存在了,只是沒有作爲API暴露出來。SynchronousQueue有一個特性:它自己不存在容量,只能進行線程之間的元素傳送。SynchronousQueue在執行offer操做時,若是沒有其餘線程執行poll,則直接返回false.線程之間元素傳送正是經過transfer方法完成的。

TransferQueue相比SynchronousQueue用處更廣、更好用,由於你能夠決定是使用BlockingQueue的方法(例如put方法)仍是確保一次傳遞完成(即transfer方法)。在隊列中已有元素的狀況下,調用transfer方法,能夠確保隊列中被傳遞元素以前的全部元素都能被處理。

從圖中咱們能夠知道實現了TransferQueue的類有:

  • LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列。

好了,隊列的API先說到這裏,下面我會另起一文重點說說阻塞隊列的那些個實現類的原理。

相關文章
相關標籤/搜索