Java中的阻塞佇列

pedro7發表於2022-02-03

Java中的阻塞佇列

1. 什麼是阻塞佇列

阻塞佇列相比普通佇列,支援下面兩個操作:

  • 支援阻塞的插入方法。佇列滿時,插入元素的執行緒可以阻塞等待佇列變為不滿。
  • 支援阻塞的移除方法。佇列為空時,獲取元素的執行緒可以阻塞等待佇列變為非空。

阻塞佇列常常用於生產者消費者場景。

在阻塞佇列不可用時,這兩個附加操作提供了四種處理方式:

方法/處理方式 丟擲異常 返回特殊值 一直阻塞 超時退出
插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
檢查方法 element() peek() 不可用 不可用
  • 丟擲異常:當佇列滿時再插入元素,會丟擲IllegalStateException("Queuefull")異常;當佇列空時再獲取元素,會丟擲NoSuchElementException異常。
  • 返回特殊值:當往佇列插入元素時,插入成功返回 true。當從佇列取出元素時,沒有則返回 null。
  • 一直阻塞:當阻塞佇列滿時,如果put元素,佇列會一直阻塞生產者執行緒,直到佇列可用或者響應中斷退出。當佇列空時,如果消費者執行緒從佇列take元素,佇列會阻塞消費者執行緒,知道佇列不為空。
  • 超時退出:當阻塞佇列滿時,如果生產者執行緒往佇列裡插入元素,佇列會阻塞生產者執行緒一段時間,如果超過指定時間,生產者執行緒就會退出。

2. Java中的阻塞佇列

JDK7 提供了下列 7 個阻塞佇列:

  • ArrayBlockingQueue。一個由陣列結構組成的有界阻塞佇列。

  • LinkedBlockingQueue。一個由連結串列結構組成的有界阻塞佇列。

  • PriorityBlockingQueue。一個支援優先順序排序的無界阻塞佇列。

  • DelayQueue。一個使用優先順序佇列實現的無界阻塞佇列。

  • SynchronousQueue。一個不儲存元素的阻塞佇列。

  • LinkedTransferQueue。一個由連結串列結構組成的無界阻塞佇列。

  • LinkedBlockingDeque。一個由連結串列結構組成的雙向阻塞佇列。

2.1 ArrayBlockingQueue

ArrayBlockingQueue是一個陣列實現的有界阻塞佇列,按照 FIFO 原則對元素排序。

預設狀態下執行緒競爭該佇列是不公平的,但可以用以下程式碼建立一個公平的阻塞佇列:

ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true);

公平性使用一個公平的可重入鎖實現的。

2.2 LinkedBlockingQueue

LinkedBlockingQueue是一個連結串列實現的有界阻塞佇列。

預設和最大長度是Integer.MAX_VALUE,按照先入先出原則進行排序。

2.3 PriorityBlockingQueue

PriorityBlockingQueue是支援優先順序的無界阻塞佇列,預設情況下元素自然順序升序排列,也可以自定義Comparator

2.4 DelayQueue

DelayQueue是一個支援延時獲取元素的無界阻塞佇列。佇列使用PriorityQueue實現,佇列中的元素必須實現Delayed介面,在建立元素時指定多久才能從佇列中獲取當前元素。

可以用在以下場景:

  • 快取系統的設計。可以用DelayQueue儲存快取元素的有效期,使用一個執行緒迴圈查詢DelayQueue,一旦能獲取元素,說明有效期到了。
  • 定時任務排程。可以用DelayQueue儲存當前將執行的任務和執行時間,一旦從DelayQueue獲取任務就開始執行,比如TimerQueue就是用DelayQueue實現的。

2.5 SynchronousQueue

SynchronousQueue是一個不儲存元素的阻塞佇列,每一個put操作必須等待一個take操作,否則不能繼續put

SynchronousQueue可以看成一個傳球手,負責把生產者執行緒處理的資料直接傳遞給消費者執行緒。佇列本身不儲存元素,非常適合傳遞性場景,吞吐量很高。

2.6 LinkedTransferQueue

LinkedTransferQueue是一個連結串列結構的無界阻塞佇列。相對於其他阻塞佇列,LinkedTransferQueue多了tryTransfer()transfer()方法。

transfer()方法

如果當前有消費者正在等待接收元素,比如take()方法或poll()方法,transfer()方法可以把生產者傳入的元素立即transfer()給消費者。如果沒有消費者在等待接收元素,transfer()方法會將元素存放在佇列的tail節點,並等到該元素被消費者消費了才返回。

tryTransfer()方法

tryTransfer()方用來試探生產者傳入的元素能否直接傳給消費者。如果沒有消費者等待接收元素,就返回false

tryTransfer()方法無論消費者是否接收,方法立即返回,而transfer()方法必須等待消費者消費了才返回。

2.7 LinkedBlockingDeque

LinkedBlockingDeque是一個由連結串列結構組成的雙向阻塞佇列。

由於是雙向佇列,多了addFirst()addLast()offerFirst()等方法。

這個佇列可以用於工作竊取模式中。

3. 阻塞佇列實現原理

JDK 使用通知模式實現阻塞佇列。

  • 當生產者往滿的佇列裡新增元素時,會阻塞住生產者。

  • 當消費者消費了一個佇列中的元素後,會通知生產者當前佇列可用。

上面的兩步在 JDK 中使用了 Condition 實現。

當往佇列裡插入元素,而佇列不可用時,阻塞生產者主要通過LockSupport.park(this)來實現。

相關文章