Java中幾個常用併發佇列比較 | Baeldung

banq發表於2020-08-20

在多執行緒應用程式中,佇列需要處理多個併發的生產者-消費者方案。正確選擇併發佇列對於在我們的演算法中實現良好效能至關重要。 
首先,我們將看到阻塞佇列和非阻塞佇列之間的一些重要區別。然後,我們將看一些實現和最佳實踐。
 

BlockingQueue提供了一種簡單的執行緒安全機制。在此佇列中,執行緒需要等待佇列的可用性。生產者將在新增元素之前等待可用容量,而消費者將等待直到佇列為空。為了實現這種阻塞機制,BlockingQueue介面在常規Queue函式的基礎上提供了兩個函式:put和take。這些功能等效於標準Queue中的add和remove。
 

ArrayBlockingQueue
此佇列在內部使用陣列。因此,它是一個有界佇列,這意味著它具有固定的大小。適合生產者/消費者比率通常很低情況,我們將耗時的任務分配給多個worker。由於此佇列不能無限增長,因此如果出現記憶體問題,需要將大小限制將作為安全閾值。
ArrayBlockingQueue對put和take操作都使用一個鎖。這樣可以確保不覆蓋條目,但會降低效能。
 

LinkedBlockingQueue

LinkedBlockingQueue使用連結串列變體,其中每個佇列專案是一個新的節點。雖然這使佇列在原則上不受限制,但仍然具有Integer.MAX_VALUE的硬限制。我們可以使用建構函式LinkedBlockingQueue(int capacity)設定佇列大小。
佇列使用不同的鎖進行put和take操作。因此兩種操作可以並行完成並提高了吞吐量。
由於LinkedBlockingQueue可以是有界的或無界的,為什麼我們還要使用ArrayBlockingQueue?每次在佇列中新增或刪除專案時,LinkedBlockingQueue都需要分配和取消分配節點。因此,如果佇列快速增長和快速收縮,則  ArrayBlockingQueue可能是更好的選擇。
據說LinkedBlockingQueue的效能是不可預測的。換句話說,我們始終需要剖析我們的方案以確保我們使用正確的資料結構。
 

PriorityBlockingQueue
當我們需要按特定順序消費資料時,PriorityBlockingQueue是我們的首選解決方案。為此,PriorityBlockingQueue使用基於陣列的二進位制堆。
儘管在內部使用單個鎖定機制,但是take操作可以與put操作同時進行。使用簡單的自旋鎖可以實現這一點。
一個典型的用例是使用具有不同優先順序的任務。我們不希望低優先順序的任務代替高優先順序的任務。
 

DelayQueue
當使用者只能take過期的資料專案時,我們使用DelayQueue 。有趣的是,它在內部使用PriorityQueue來按資料專案的到期時間對其進行排序。
由於這不是通用佇列,因此它無法涵蓋ArrayBlockingQueue或LinkedBlockingQueue那樣多的場景。例如,我們可以使用此佇列來實現一個簡單的事件迴圈,類似於在NodeJS中找到的事件迴圈。我們將非同步任務放在佇列中,以便在它們到期時進行後續處理。
 

LinkedTransferQueue

LinkedTransferQueue引入一個transfer 方法。儘管其他佇列通常在生產或消費資料專案時阻塞,但LinkedTransferQueue 允許生產者等待資料專案的消費。
當我們需要保證放入佇列中的某個特定專案已被消費者take拿走時,可以使用LinkedTransferQueue。同樣,我們可以使用此佇列實現簡單的反壓演算法。實際上,透過阻止生產者直到消費,消費者可以驅動所產生的訊息流。
 

SynchronousQueue
普通佇列通常包含許多資料專案,但SynchronousQueue最多始終只有一個專案。換句話說,我們需要將SynchronousQueue視為在兩個執行緒之間交換某些資料的簡單方法。
當我們有兩個需要訪問共享狀態的執行緒時,我們通常將它們與CountDownLatch或其他同步機制同步。透過使用SynchronousQueue,我們可以避免執行緒的這種手動同步。
 

ConcurrentLinkedQueue

ConcurrentLinkedQueue是本文唯一的非阻塞佇列,因此,它提供了一種“免等待”演算法,其中add和poll保證是執行緒安全的,並立即返回。該佇列使用CAS(Compare-And-Swap)代替鎖。
在內部,它基於Maged M. Michael和Michael L. Scott的簡單,快速和實用的非阻塞和阻塞併發佇列演算法
對於經常禁止使用阻塞資料結構的現代反應系統,它是理想的選擇。
另一方面,如果我們的消費者最終陷入迴圈等待,我們可能應該選擇阻塞佇列作為更好的選擇。

相關文章