23-JUC中的阻塞隊列

Queue接口

隊列是一種先進先出(FIFO)的數據結構,java中用Queue接口來表示隊列

Queue接口中定義了6個方法

public interface Queue<E> extends Collection<E> {
    boolean add(e);
    boolean offer(E e);
    E remove();
    E poll();
    E element();
    E peek();
}

每個Queue方法都有兩種形式:
(1)如果操作失敗則拋出異常,
(2)如果操作失敗,則返回特殊值(null或false,具體取決於操作),接口的常規結構如下表所示。

操作類型 拋出異常 返回特殊值
插入 add(e) offer(e)
移除 remove() poll()
檢查 element() peek()

Queue從Collection繼承的add方法插入一個元素,除非它違反了隊列的容量限制,在這種情況下它會拋出IllegalStateException;offer方法與add不同之處僅在於它通過返回false來表示插入元素失敗。

remove和poll方法都移除並返回隊列的頭部,確切地移除哪個元素是由具體的實現來決定的,僅當隊列爲空時,remove和poll方法的行爲纔有所不同,在這些情況下,remove拋出NoSuchElementException,而poll返回null。

element和peek方法返回隊列頭部的元素,但不移除,它們之間的差異與remove和poll的方式完全相同,如果隊列爲空,則element拋出NoSuchElementException,而peek返回null。

隊列一般不要插入空元素。

BlockingQueue接口

BlockingQueue位於juc中,熟稱阻塞隊列, 阻塞隊列首先它是一個隊列,繼承Queue接口,是隊列就會遵循先進先出(FIFO)的原則,又因爲它是阻塞的,故與普通的隊列有兩點區別:

  1. 當一個線程向隊列裏面添加數據時,如果隊列是滿的,那麼將阻塞該線程,暫停添加數據
  2. 當一個線程從隊列裏面取出數據時,如果隊列是空的,那麼將阻塞該線程,暫停取出數據

BlockingQueue相關方法:

操作類型 拋出異常 返回特殊值 一直阻塞 超時退出
插入 add(e) offer(e) put(e) offer(e,timeuout,unit)
移除 remove() poll() take() poll(timeout,unit)
檢查 element() peek() 不支持 不支持
  • 3個可能會有異常的方法,add、remove、element;這3個方法不會阻塞(是說隊列滿或者空的情況下是否會阻塞);隊列滿的情況下,add拋出異常;隊列爲空情況下,remove、element拋出異常
    offer、poll、peek 也不會阻塞(是說隊列滿或者空的情況下是否會阻塞);隊列滿的情況下,offer返回false;隊列爲空的情況下,poll、peek返回null
  • 隊列滿的情況下,調用put方法會導致當前線程阻塞
  • 隊列爲空的情況下,調用take方法會導致當前線程阻塞
  • offer(e,timeuout,unit),超時之前,插入成功返回true,否者返回false
  • poll(timeout,unit),超時之前,獲取到頭部元素並將其移除,返回true,否者返回false

BlockingQueue常見的實現類

在這裏插入圖片描述

ArrayBlockingQueue

基於數組的阻塞隊列實現,其內部維護一個定長的數組,用於存儲隊列元素。線程阻塞的實現是通過ReentrantLock來完成的,數據的插入與取出共用同一個鎖,因此ArrayBlockingQueue並不能實現生產、消費同時進行。而且在創建ArrayBlockingQueue時,我們還可以控制對象的內部鎖是否採用公平鎖,默認採用非公平鎖

有界阻塞隊列,內部使用數組存儲元素,有2個常用構造方法:

//capacity表示容量大小,默認內部採用非公平鎖
public ArrayBlockingQueue(int capacity)
//capacity:容量大小,fair:內部是否是使用公平鎖
public ArrayBlockingQueue(int capacity, boolean fair)
LinkedBlockingQueue

基於單向鏈表的阻塞隊列實現,在初始化LinkedBlockingQueue的時候可以指定大小,也可以不指定,默認類似一個無限大小的容量(Integer.MAX_VALUE),不指隊列容量大小也是會有風險的,一旦數據生產速度大於消費速度,系統內存將有可能被消耗殆盡,因此要謹慎操作。另外LinkedBlockingQueue中用於阻塞生產者、消費者的鎖是兩個(鎖分離),因此生產與消費是可以同時進行的

內部使用單向鏈表實現的阻塞隊列,3個構造方法:

//默認構造方法,容量大小爲Integer.MAX_VALUE
public LinkedBlockingQueue();
//創建指定容量大小的LinkedBlockingQueue
public LinkedBlockingQueue(int capacity);
//容量爲Integer.MAX_VALUE,並將傳入的集合丟入隊列中
public LinkedBlockingQueue(Collection<? extends E> c);
PriorityBlockingQueue

一個支持優先級排序的無界阻塞隊列,進入隊列的元素會按照優先級進行排序

無界的優先級阻塞隊列,內部使用數組存儲數據,達到容量時,會自動進行擴容,放入的元素會按照優先級進行排序,4個構造方法:

//默認構造方法,默認初始化容量是11
public PriorityBlockingQueue();
//指定隊列的初始化容量
public PriorityBlockingQueue(int initialCapacity);
//指定隊列的初始化容量和放入元素的比較器
public PriorityBlockingQueue(int initialCapacity,Comparator<? super E> comparator);
//傳入集合放入來初始化隊列,傳入的集合可以實現SortedSet接口或者PriorityQueue接口進行排序,如果沒有實現這2個接口,按正常順序放入隊列
public PriorityBlockingQueue(Collection<? extends E> c);

優先級隊列放入元素的時候,會進行排序,所以我們需要指定排序規則,有2種方式:

  • 創建PriorityBlockingQueue指定比較器Comparator
  • 放入的元素需要實現Comparable接口

上面2種方式必須選一個,如果2個都有,則走第一個規則排序。

SynchronousQueue

同步阻塞隊列,SynchronousQueue沒有容量,與其他BlockingQueue不同,SynchronousQueue是一個不存儲元素的BlockingQueue,每一個put操作必須要等待一個take操作,否則不能繼續添加元素,反之亦然

DelayQueue

DelayQueue是一個支持延時獲取元素的無界阻塞隊列,裏面的元素全部都是“可延期”的元素,列頭的元素是最先“到期”的元素,如果隊列裏面沒有元素到期,是不能從列頭獲取元素的,哪怕有元素也不行,也就是說只有在延遲期到時才能夠從隊列中取元素

Delayed繼承了Comparable接口,這個接口是用來做比較用的,DelayQueue內部使用PriorityQueue來存儲數據的,PriorityQueue是一個優先級隊列,丟入的數據會進行排序,排序方法調用的是Comparable接口中的方法

LinkedTransferQueue

LinkedTransferQueue是基於鏈表的FIFO無界阻塞隊列,它出現在JDK7中,Doug Lea 大神說LinkedTransferQueue是一個聰明的隊列,它是ConcurrentLinkedQueue、SynchronousQueue(公平模式下)、無界的LinkedBlockingQueues等的超集,LinkedTransferQueue包含了ConcurrentLinkedQueue、SynchronousQueue、LinkedBlockingQueues三種隊列的功能

LinkedTransferQueue是一個由鏈表結構組成的無界阻塞TransferQueue隊列。相對於其他阻塞隊列,LinkedTransferQueue多了tryTransfer和transfer方法。

LinkedTransferQueue類繼承自AbstractQueue抽象類,並且實現了TransferQueue接口:

public interface TransferQueue<E> extends BlockingQueue<E> {
    // 如果存在一個消費者已經等待接收它,則立即傳送指定的元素,否則返回false,並且不進入隊列。
    boolean tryTransfer(E e);
    // 如果存在一個消費者已經等待接收它,則立即傳送指定的元素,否則等待直到元素被消費者接收。
    void transfer(E e) throws InterruptedException;
    // 在上述方法的基礎上設置超時時間
    boolean tryTransfer(E e, long timeout, TimeUnit unit)
        throws InterruptedException;
    // 如果至少有一位消費者在等待,則返回true
    boolean hasWaitingConsumer();
    // 獲取所有等待獲取元素的消費線程數量
    int getWaitingConsumerCount();
}

上面的這些方法,transfer(E e)方法和SynchronousQueue的put方法類似,都需要等待消費者取走元素,否者一直等待。其他方法和ArrayBlockingQueue、LinkedBlockingQueue中的方法類似。

參考

第25天:掌握JUC中的阻塞隊列

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章