1. 簡單的開篇
LinkedBlockingQueue 和 ConcurrentLinkedQueue 是 Java 高并發場景中最常使用的佇列,盡管這兩個佇列經常被用作并發場景的資料結構,但它們之間仍有細微的特征和行為差異,
在這篇文章中,我將和大家一起探討這兩者之間的異同點,歡迎大家在留言討論~
2. LinkedBlockingQueue
首先 LinkedBlockingQueue 是一個 “可選且有界” 的阻塞佇列實作,你可以根據需要指定佇列的大小,
接下來,我將創建一個LinkedBlockingQueue,它最多可以包含100個元素:
BlockingQueue<Integer> boundedQueue = new LinkedBlockingQueue<>(100);
當然,我們也可以通過不指定大小,來創建一個無界的 LinkedBlockingQueue:
BlockingQueue<Integer> unboundedQueue = new LinkedBlockingQueue<>();
無界隊串列示在創建時未指定佇列的大小,因此,佇列可以隨著元素的添加而動態增長,但是,如果沒有剩余記憶體,則佇列將拋出 java.lang.OutOfMemory 錯誤,
這里留下一個問題給大家思考: 創建無界佇列是好還是壞呢?
我們還可以從現有的集合來創建 LinkedBlockingQueue:
Collection<Integer> listOfNumbers = Arrays.asList(1,2,3,4,5);
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(listOfNumbers);
LinkedBlockingQueue 實作了BlockingQueue介面,該介面為它提供了阻塞性質,
阻塞隊串列示如果訪問執行緒已滿(當佇列有界時)或變為空,則佇列將阻塞該執行緒,如果佇列已滿,則添加新元素將阻塞訪問執行緒,除非新元素有可用空間,類似地,如果佇列為空,則訪問元素會阻塞呼叫執行緒:
ExecutorService executorService = Executors.newFixedThreadPool(1);
LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
executorService.submit(() -> {
try {
queue.take();
}
catch (InterruptedException e) {
// exception handling
}
});
在上面的代碼片段中,我們正在訪問一個空佇列,因此,take() 方法阻塞呼叫執行緒,
LinkedBlockingQueue 的阻塞特性與一些開銷相關,這個代價是因為每個put或take操作在生產者執行緒或使用者執行緒之間都是鎖爭用的,因此,在許多生產者和消費者的情況下,put和take 動作可能會慢一些,
3. ConcurrentLinkedQueue
首先宣告,ConcurrentLinkedQueue 是一個無邊界、執行緒安全且無阻塞的佇列
創建一個空的 ConcurrentLinkedQueue:
ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();
同上面一樣,我們也可以從現有集合創建 ConcurrentLinkedQueue:
Collection<Integer> listOfNumbers = Arrays.asList(1,2,3,4,5);
ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>(listOfNumbers);
不同于 LinkedBlockingQueue, ConcurrentLinkedQueue是非阻塞的佇列,因此,即使佇列為空(empty),它也不會阻塞執行緒,相反,它會回傳 空(null) ,雖然它是無界的,但如果沒有額外的記憶體來添加新元素,它依舊會拋出 java.lang.OutOfMemory 錯誤,
除了非阻塞之外,ConcurrentLinkedQueue還有其他特性,
在任何生產者-消費者場景中,消費者都不會滿足于生產者;但是,多個生產者將相互競爭:
int element = 1;
ExecutorService executorService = Executors.newFixedThreadPool(2);
ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>();
Runnable offerTask = () -> queue.offer(element);
Callable<Integer> pollTask = () -> {
while (queue.peek() != null) {
return queue.poll().intValue();
}
return null;
};
executorService.submit(offerTask);
Future<Integer> returnedElement = executorService.submit(pollTask);
assertThat(returnedElement.get().intValue(), is(equalTo(element)));
第一個任務 offerTask 向佇列中添加元素,第二個任務 pollTask 從佇列中檢索元素,pollTask 首先檢查佇列中的元素,因為ConcurrentLinkedQueue是非阻塞的,并且可以回傳null值,
4. 求同
LinkedBlockingQueue 和 ConcurrentLinkedQueue 都是佇列實作,并具有一些共同特征,讓我們討論一下這兩個佇列的相似之處:
- 都 實作 Queue 介面
- 它們都使用 linked nodes 存盤節點
- 都適用于并發訪問場景
5. 存異
盡管這兩種佇列都有某些相似之處,但也有一些實質性的特征差異:
| 特性 | LinkedBlockingQueue | ConcurrentLinkedQueue |
|---|---|---|
| 阻塞性 | 阻塞佇列,并實作blocking queue介面 | 非阻塞佇列,不實作blocking queue介面 |
| 佇列大小 | 可選的有界佇列,這意味著可以在創建期間定義佇列大小 | 無邊界佇列,并且沒有在創建期間指定佇列大小的規定 |
| 鎖特性 | 基于鎖的佇列 | 無鎖佇列 |
| 演算法 | 鎖的實作基于 “雙鎖佇列(two lock queue)” 演算法 | 依賴于Michael&Scott演算法來實作無阻塞、無鎖佇列 |
| 實作 | 在 雙鎖佇列 演算法機制中,LinkedBlockingQueue使用兩種不同的鎖,putLock和takeLock,put/take操作使用第一個鎖型別,take/poll操作使用另一個鎖型別 | 使用CAS(Compare And Swap)進行操作 |
| 阻塞行為 | 當佇列為空時,它會阻塞訪問執行緒 | 當佇列為空時回傳 null,它不會阻塞訪問執行緒 |
6. 總結才能進步
首先,我們分別討論了這兩種佇列實作及其一些特性、相似性、以及它們之間的差異,這樣的比較,是否讓你對這兩種佇列有了更深刻的印象?
公眾號: 鍋外的大佬 ,歡迎加群~
博客地址: http://www.developlee.top
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/65095.html
標籤:Java
