Java 阻塞队列中的常用方法及区别

Java 阻塞队列中的常用方法及区别

前言

在阻塞队列中有很多方法,它们的功能都非常相似,所以非常有必要对这些类似的方法进行辨析,本章采用分类的方式对阻塞队列中常见的方法进行讨论。

BlockingQueue 中最常用的和添加、删除相关的 8 个方法,把它们分为三组。这三组方法由于功能很类似,所以比较容易混淆。它们的区别仅在于特殊情况:当队列满了无法添加元素,或者是队列空了无法移除元素时,不同组的方法对于这种特殊情况会有不同的处理方式:

  • 抛出异常:add、remove、element
  • 返回结果但不抛出异常:offer、poll、peek
  • 阻塞:put、take

项目环境

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 方法插入元素时,如果队列没有满,那就和普通的插入一样是正常的插入,但是如果队列已满,那么就无法继续插入,则阻塞,直到队列里有了空闲空间。如果后续队列有了空闲空间,比如消费者消费了一个元素,那么此时队列就会解除阻塞状态,并把需要添加的数据添加到队列中。过程如图所示:

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 方法的时候,队列里无数据,则阻塞,直到队列里有数据。一旦队列里有数据了,就会立刻解除阻塞状态,并且取到数据。过程如图所示:

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 讲》- 徐隆曦
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章