Java 阻塞队列中的常用方法及区别
文章目录
- Java 阻塞队列中的常用方法及区别
- 前言
- 项目环境
- 1.第一组方法
- 1.1 add 方法
- 1.2 remove 方法
- 1.3 element 方法
- 2.第二组方法
- 2.1 offer 方法
- 2.2 poll 方法
- 2.3 peek 方法
- 3.第三组方法
- 3.1 put 方法
- 3.2 take 方法
- 4.总结
前言
在阻塞队列中有很多方法,它们的功能都非常相似,所以非常有必要对这些类似的方法进行辨析,本章采用分类的方式对阻塞队列中常见的方法进行讨论。
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 个方法,按照各自的特点分为以下三组
- 第一组的特点是在无法正常执行的情况下抛出异常
- 第二组的特点是在无法正常执行的情况下不抛出异常,但会用返回值提示运行失败
- 第三组的特点是在遇到特殊情况时让线程陷入阻塞状态,等到可以运行再继续执行