Java 阻塞隊列中的常用方法及區別
文章目錄
前言
在阻塞隊列中有很多方法,它們的功能都非常相似,所以非常有必要對這些類似的方法進行辨析,本章採用分類的方式對阻塞隊列中常見的方法進行討論。
BlockingQueue 中最常用的和添加、刪除相關的 8 個方法,把它們分爲三組。這三組方法由於功能很類似,所以比較容易混淆。它們的區別僅在於特殊情況:當隊列滿了無法添加元素,或者是隊列空了無法移除元素時,不同組的方法對於這種特殊情況會有不同的處理方式:
- 拋出異常:add、remove、element
- 返回結果但不拋出異常:offer、poll、peek
- 阻塞:put、take
項目環境
- jdk 1.8
- github 地址:https://github.com/huajiexiewenfeng/java-concurrent
- 本章模塊:blockingqueue
1.第一組方法
1.1 add 方法
add 方法是往隊列裏添加一個元素,如果隊列滿了,就會拋出異常來提示隊列已滿。示例代碼如下:
public class BlockingQueueMethodsDemo {
public static void main(String[] args) {
BlockingQueue<String> bQueue = new ArrayBlockingQueue(3);
// 添加方法
addMethod(bQueue);
}
private static void addMethod(BlockingQueue<String> bQueue) {
bQueue.add("1");
bQueue.add("2");
bQueue.add("3");
bQueue.add("4");
}
}
執行結果:
Exception in thread "main" java.lang.IllegalStateException: Queue full
at java.util.AbstractQueue.add(AbstractQueue.java:98)
at java.util.concurrent.ArrayBlockingQueue.add(ArrayBlockingQueue.java:312)
at com.csdn.blockingqueue.BlockingQueueMethodsDemo.addMethod(BlockingQueueMethodsDemo.java:22)
at com.csdn.blockingqueue.BlockingQueueMethodsDemo.main(BlockingQueueMethodsDemo.java:15)
顯然在添加第 4 個元素的時候,超過了容量的限制,拋出異常。
1.2 remove 方法
remove 方法的作用是刪除元素,如果我們刪除的隊列是空的,由於裏面什麼都沒有,所以也無法刪除任何元素,那麼 remove 方法就會拋出異常。示例代碼如下:
private static void removeMethod(BlockingQueue<String> bQueue) {
bQueue.add("1");
bQueue.add("2");
bQueue.remove();
bQueue.remove();
bQueue.remove();
}
執行結果:
Exception in thread "main" java.util.NoSuchElementException
at java.util.AbstractQueue.remove(AbstractQueue.java:117)
at com.csdn.blockingqueue.BlockingQueueMethodsDemo.removeMethod(BlockingQueueMethodsDemo.java:34)
at com.csdn.blockingqueue.BlockingQueueMethodsDemo.main(BlockingQueueMethodsDemo.java:19)
當調用第三個 remove 方法是,隊列中的元素爲空,拋出異常。
1.3 element 方法
element 方法是返回隊列的頭部節點,但是並不刪除。和 remove 方法一樣,如果我們用這個方法去操作一個空隊列,想獲取隊列的頭結點,可是由於隊列是空的,我們什麼都獲取不到,會拋出和前面 remove 方法一樣的異常:NoSuchElementException。示例代碼如下:
private static void elementMethod(BlockingQueue<String> bQueue) {
bQueue.add("1");
bQueue.add("2");
System.out.println("元素:"+bQueue.element());
bQueue.remove();
System.out.println("元素:"+bQueue.element());
bQueue.remove();
System.out.println("元素:"+bQueue.element());
}
執行結果:
元素:1
元素:2
Exception in thread "main" java.util.NoSuchElementException
at java.util.AbstractQueue.element(AbstractQueue.java:136)
at com.csdn.blockingqueue.BlockingQueueMethodsDemo.elementMethod(BlockingQueueMethodsDemo.java:45)
at com.csdn.blockingqueue.BlockingQueueMethodsDemo.main(BlockingQueueMethodsDemo.java:20)
2.第二組方法
實際上我們通常並不想看到第一組方法拋出的異常,這時我們可以優先採用第二組方法。第二組方法相比於第一組而言要友好一些,當發現隊列滿了無法添加,或者隊列爲空無法刪除的時候,第二組方法會給一個提示,而不是拋出一個異常。
2.1 offer 方法
offer 方法用來插入一個元素,並用返回值來提示插入是否成功。如果添加成功會返回 true,而如果隊列已經滿了,此時繼續調用 offer 方法的話,它不會拋出異常,只會返回一個錯誤提示:false。示例代碼如下:
private static void offerMethod(BlockingQueue<String> bQueue) {
System.out.println(bQueue.offer("1"));
System.out.println(bQueue.offer("2"));
System.out.println(bQueue.offer("3"));
System.out.println(bQueue.offer("4"));
System.out.println("隊列長度:" + bQueue.size());
}
執行結果:
true
true
true
false
隊列長度:3
2.2 poll 方法
poll 方法和第一組的 remove 方法是對應的,作用也是移除並返回隊列的頭節點。但是如果當隊列裏面是空的,沒有任何東西可以移除的時候,便會返回 null 作爲提示。正因如此,我們是不允許往隊列中插入 null 的,否則我們沒有辦法區分返回的 null 是一個提示還是一個真正的元素。示例代碼如下:
private static void pollMethod(BlockingQueue<String> bQueue) {
bQueue.offer("1");
bQueue.offer("2");
System.out.println("元素:" + bQueue.poll());
System.out.println("元素:" + bQueue.poll());
System.out.println("元素:" + bQueue.poll());
}
執行結果:
元素:1
元素:2
元素:null
2.3 peek 方法
peek 方法和第一組的 element 方法是對應的,意思是返回隊列的頭元素但並不刪除。如果隊列裏面是空的,它便會返回 null 作爲提示。示例代碼如下:
private static void peekMethod(BlockingQueue<String> bQueue) {
bQueue.offer("1");
bQueue.offer("2");
System.out.println("元素:" + bQueue.peek());
bQueue.poll();
System.out.println("元素:" + bQueue.peek());
bQueue.poll();
System.out.println("元素:" + bQueue.peek());
}
執行結果:
元素:1
元素:2
元素:null
3.第三組方法
第三組是阻塞隊列最大特色的 put 和 take 方法,這兩個方法在 《什麼是阻塞隊列(BlockingQueue)?》 中討論過,這裏就直接將內容複製過來,加上簡單的示例代碼。
3.1 put 方法
put 方法插入元素時,如果隊列沒有滿,那就和普通的插入一樣是正常的插入,但是如果隊列已滿,那麼就無法繼續插入,則阻塞,直到隊列裏有了空閒空間。如果後續隊列有了空閒空間,比如消費者消費了一個元素,那麼此時隊列就會解除阻塞狀態,並把需要添加的數據添加到隊列中。過程如圖所示:
示例代碼:
private static void putMethod(BlockingQueue<String> bQueue) {
try {
bQueue.put("1");
bQueue.put("2");
bQueue.put("3");
bQueue.put("4");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
當 bQueue.put("4");
執行的時候,由於隊列容量爲 3,主線程會一直阻塞。
3.2 take 方法
take 方法的功能是獲取並移除隊列的頭結點,通常在隊列裏有數據的時候是可以正常移除的。可是一旦執行 take 方法的時候,隊列裏無數據,則阻塞,直到隊列裏有數據。一旦隊列裏有數據了,就會立刻解除阻塞狀態,並且取到數據。過程如圖所示:
示例代碼:
private static void takeMethod(BlockingQueue<String> bQueue) {
try {
System.out.println("元素:" +bQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
由於隊列爲空,執行之後,主線程會一直阻塞。
4.總結
組別 | 方法 | 含義 | 特點 |
---|---|---|---|
第一組 | add | 添加一個元素 | 如果隊列滿,拋出異常 IllegalStateException |
第一組 | remove | 返回並刪除隊列的頭節點 | 如果隊列空,拋出異常 NoSuchElementException |
第一組 | element | 返回隊列頭節點 | 如果隊列空,拋出異常 NoSuchElementException |
第二組 | offer | 添加一個元素 | 添加成功,返回 true,添加失敗,返回 false |
第二組 | poll | 返回並刪除隊列的頭節點 | 如果隊列空,返回 null |
第二組 | peek | 返回隊列頭節點 | 如果隊列空,返回 null |
第三組 | put | 添加一個元素 | 如果隊列滿,阻塞 |
第三組 | take | 返回並刪除隊列的頭節點 | 如果隊列空,阻塞 |
本文討論了阻塞隊列中常見的 8 個方法,按照各自的特點分爲以下三組
- 第一組的特點是在無法正常執行的情況下拋出異常
- 第二組的特點是在無法正常執行的情況下不拋出異常,但會用返回值提示運行失敗
- 第三組的特點是在遇到特殊情況時讓線程陷入阻塞狀態,等到可以運行再繼續執行
5.參考
- 《Java 併發編程 78 講》- 徐隆曦