Java多线程与高并发专题——阻塞队列常用方法与区别

devtools/2025/3/12 14:16:29/

引入

上一篇文章我们了解了阻塞队列,在阻塞队列中有很多方法,而且它们都非常相似,所以非常有必要对这些类似的方法进行辨析,所以借鉴其源码注释里面的分类方式,把阻塞队列中常见的方法进行梳理和讲解。

其源码注释中的方法摘要如下:

操作\处理方式抛出异常特殊值阻塞超时
插入add(e)offer(e)put(e)offer(e, time, unit)
移除remove()poll()take()poll(time, unit)
检查element()peek()\\

我们重点看前面三组,可以看到每组方法都和添加、移除元素相关。这三组方法由于功能很类似,所以比较容易混淆。它们的区别仅在于特殊情况:当队列满了无法添加元素,或者是队列空了无法移除元素时,不同组的方法对于这种特殊情况会有不同的处理方式:

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

第一组(add、remove、element)

add

add 方法是往队列里添加一个元素,如果队列满了,就会抛出异常来提示队列已满。

源码注释如下:

Inserts the specified element into this queue if it is possible to do so immediately without violating capacity restrictions, returning true upon success and throwing an IllegalStateException if no space is currently available. When using a capacity-restricted queue, it is generally preferable to use offer.

翻译:

如果不会违反容量限制,立即将指定元素插入此队列,成功时返回 true,如果当前没有空间,则抛出 IllegalStateException。在使用容量受限的队列时,通常更倾向于使用 offer。

示例代码如下:

java">    /*** 此方法用于测试向一个容量为2的阻塞队列中添加元素。* 当队列已满时,继续添加元素会抛出异常。*/@Testpublic void addTest() {// 创建一个容量为2的ArrayBlockingQueue实例BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(2);// 向队列中添加元素1blockingQueue.add(1);// 再次向队列中添加元素1blockingQueue.add(1);// 由于队列已满,继续添加元素1会抛出IllegalStateException异常blockingQueue.add(1);}

在这段代码中,我们创建了一个容量为 2 的 BlockingQueue,并且尝试往里面放 3 个值,超过了容量上限,那么在添加第三个值的时候就会得到异常:

remove

remove 方法的作用是删除元素,如果我们删除的队列是空的,由于里面什么都没有,所以也无法删除任何元素,那么 remove 方法就会抛出异常。

源码注释如下:

Removes a single instance of the specified element from this queue, if it is present. More formally, removes an element e such that o. equals(e), if this queue contains one or more such elements. Returns true if this queue contained the specified element (or equivalently, if this queue changed as a result of the call).

翻译:

如果存在,从这个队列中移除一个指定元素的实例。更正式地说,如果这个队列中包含一个或多个这样的元素,则移除满足 o.equals(e) 的元素 e。如果这个队列包含指定的元素(或者等价地,如果这个队列因为调用而发生了变化),则返回 true。

示例代码如下: 

java">    /*** 测试 ArrayBlockingQueue 的 remove 方法。* 此方法创建一个容量为2的 ArrayBlockingQueue,向其中添加两个元素,* 然后尝试移除三个元素,最后一个移除操作会抛出异常。*/@Testpublic void removeTest() {// 创建一个容量为2的 ArrayBlockingQueueArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(2);// 向队列中添加元素 1blockingQueue.add(1);// 向队列中添加元素 1blockingQueue.add(1);// 从队列中移除一个元素blockingQueue.remove();// 从队列中移除一个元素blockingQueue.remove();// 尝试从空队列中移除元素,会抛出 NoSuchElementException 异常blockingQueue.remove();}

 在这段代码中,我们往一个容量为 2 的 BlockingQueue 里放入 2 个元素,并且删除 3 个元素。在删除前面两个元素的时候会正常执行,因为里面依然有元素存在,但是在删除第三个元素时,由于队列里面已经空了,所以便会抛出异常:

element

element 方法是返回队列的头部节点,但是并不删除。和 remove 方法一样,如果我们用这个方法去操作一个空队列,想获取队列的头结点,可是由于队列是空的,我们什么都获取不到,会抛出和前面remove 方法一样的异常:NoSuchElementException。

源码注释如下:

Retrieves, but does not remove, the head of this queue. This method differs from peek only in that it throws an exception if this queue is empty.

翻译:

检索但不移除此队列的头部元素。此方法与 peek 的区别在于,当队列为空时,它会抛出异常。

示例代码如下:

java">    /*** 测试 ArrayBlockingQueue 的 element 方法。* 此方法尝试从队列中获取队首元素,但不将其移除。* 如果队列为空,该方法会抛出 NoSuchElementException 异常。*/@Testpublic void elementTest() {// 创建一个容量为 2 的 ArrayBlockingQueue,用于存储 Integer 类型的元素ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(2);// 调用 element 方法尝试获取队列的队首元素blockingQueue.element();}

我们新建了一个容量为 2 的 ArrayBlockingQueue,直接调用 element 方法,由于之前没有往里面添加元素,默认为空,那么会得到异常: 

第二组(offer、poll、peek)

实际上我们通常并不想看到第一组方法抛出的异常,这时我们可以优先采用第二组方法。第二组方法相比于第一组而言要友好一些,当发现队列满了无法添加,或者队列为空无法删除的时候,第二组方法会给一个提示,而不是抛出一个异常。

offer

offer 方法用来插入一个元素,并用返回值来提示插入是否成功。如果添加成功会返回 true,而如果队列已经满了,此时继续调用 offer 方法的话,它不会抛出异常,只会返回一个错误提示:false。

源码注释如下:

Inserts the specified element into this queue if it is possible to do so immediately without violating capacity restrictions, returning true upon success and false if no space is currently available. When using a capacity-restricted queue, this method is generally preferable to add, which can fail to insert an element only by throwing an exception.

翻译:

如果不会违反容量限制,立即将指定元素插入此队列。成功时返回 true,如果当前没有空间可用则返回 false。在使用容量受限的队列时,此方法通常优于 add,因为 add 只能在插入元素失败时通过抛出异常来表示。

示例代码如下:

java">    /*** 测试 ArrayBlockingQueue 的 offer 方法。* offer 方法用于将指定元素插入到此队列的尾部(如果立即可行且不会超出此队列的容量),* 在成功时返回 true,如果此队列已满,则返回 false。*/@Testpublic void offerTest() {// 创建一个容量为 2 的 ArrayBlockingQueue,用于存储 Integer 类型的元素ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(2);// 尝试向队列中添加元素 1,输出添加结果(true 或 false)System.out.println(blockingQueue.offer(1));// 再次尝试向队列中添加元素 1,输出添加结果System.out.println(blockingQueue.offer(1));// 由于队列容量为 2,此时队列已满,再次尝试添加元素 1,输出添加结果System.out.println(blockingQueue.offer(1));}

我们创建了一个容量为 2 的 ArrayBlockingQueue,并且调用了三次 offer方法尝试添加,每次都把返回值打印出来,运行结果如下:

可以看出,前面两次添加成功了,但是第三次添加的时候,已经超过了队列的最大容量,所以会返回false,表明添加失败。

poll

poll 方法和第一组的 remove 方法是对应的,作用也是移除并返回队列的头节点。但是如果当队列里面是空的,没有任何东西可以移除的时候,便会返回 null 作为提示。正因如此,我们是不允许往队列中插入 null 的,否则我们没有办法区分返回的 null 是一个提示还是一个真正的元素。

源码注释如下:

Retrieves and removes the head of this queue, or returns null if this queue is empty.

翻译:

检索并移除此队列的头部元素,如果队列为空则返回 null。

示例代码如下:

java">    /*** 测试 ArrayBlockingQueue 的 poll 方法。* 该方法会创建一个容量为 3 的 ArrayBlockingQueue 实例,* 向队列中添加 3 个元素,然后依次从队列中取出元素并打印,* 最后尝试取出一个不存在的元素。*/@Testpublic void pollTest() {// 创建一个容量为 3 的 ArrayBlockingQueue 实例ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(3);// 向队列中添加元素 1blockingQueue.offer(1);// 向队列中添加元素 2blockingQueue.offer(2);// 向队列中添加元素 3blockingQueue.offer(3);// 从队列中取出一个元素并打印System.out.println(blockingQueue.poll());// 从队列中取出一个元素并打印System.out.println(blockingQueue.poll());// 从队列中取出一个元素并打印System.out.println(blockingQueue.poll());// 尝试从空队列中取出一个元素并打印,此时会返回 nullSystem.out.println(blockingQueue.poll());}

 在这个代码中我们创建了一个容量为 3 的 ArrayBlockingQueue,并且先往里面放入 3 个元素,然后四次调用 poll 方法,运行结果如下:

 前面三次 poll 都运行成功了,并且返回了元素内容 1、2、3,是先进先出的顺序。第四次的 poll 方法返回 null,代表此时已经没有元素可以移除了。

peek

peek 方法和第一组的 element 方法是对应的,意思是返回队列的头元素但并不删除。如果队列里面是空的,它便会返回 null 作为提示。

源码注释如下:

Retrieves, but does not remove, the head of this queue, or returns null if this queue is empty.

翻译:

检索但不移除此队列的头部元素,如果队列为空则返回 null。

示例代码如下:

java">    /*** 测试 ArrayBlockingQueue 的 peek 方法。* peek 方法用于获取队列的头部元素,但不移除该元素。* 如果队列为空,则返回 null。*/@Testpublic void peekTest() {// 创建一个容量为 2 的 ArrayBlockingQueue 实例,用于存储 Integer 类型的元素ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(2);// 调用 peek 方法获取队列头部元素并打印结果System.out.println(blockingQueue.peek());}

 运行结果:

 

 我们新建了一个空的 ArrayBlockingQueue,然后直接调用 peek,返回结果 null,代表此时并没有东西可以取出。

拓展:带超时时间的 offer 和 poll

第二组还有一些额外值得讲解的内容,offer 和 poll 都有带超时时间的重载方法。

offer(E e, long timeout, TimeUnit unit)

它有三个参数,分别是元素、超时时长和时间单位。通常情况下,这个方法会插入成功并返回 true;如果队列满了导致插入不成功,在调用带超时时间重载方法的 offer 的时候,则会等待指定的超时时间,如果时间到了依然没有插入成功,就会返回 false。

poll(long timeout, TimeUnit unit)

带时间参数的 poll 方法和 offer 类似:如果能够移除,便会立刻返回这个节点的内容;如果队列是空的就会进行等待,等待时间正是我们指定的时间,直到超时时间到了,如果队列里依然没有元素可供移除,便会返回 null 作为提示。

第三组(put、take)

第三组是我们比较熟悉的、阻塞队列最大特色的 put 和 take 方法。

put

put 方法的作用是插入元素。通常在队列没满的时候是正常的插入,但是如果队列已满就无法继续插入,这时它既不会立刻返回 false 也不会抛出异常,而是让插入的线程陷入阻塞状态,直到队列里有了空闲空间,此时队列就会让之前的线程解除阻塞状态,并把刚才那个元素添加进去。

take

take 方法的作用是获取并移除队列的头结点。通常在队列里有数据的时候会正常取出数据并删除;但是如果执行 take 的时候队列里无数据,则阻塞,直到队列里有数据;一旦队列里有数据了,就会立刻解除阻塞状态,并且取到数据。

总结

本文我们讲解了阻塞队列中常见的方法并且把它们分为了三组,每一组都有各自的特点。

  • 第一组的特点是在无法正常执行的情况下抛出异常;
  • 第二组的特点是在无法正常执行的情况下不抛出异常,但会用返回值提示运行失败;
  • 第三组的特点是在遇到特殊情况时让线程陷入阻塞状态,等到可以运行再继续执行。

我们用表格把上面 8 种方法总结如下:

方法含义特点
add添加一个元素如果队列满了,操作失败,抛出IllegalStateException
remove返回并删除队列的头元素如果队列空,删除失败,抛出NoSuchElementException
element返回队列的头元素如果队列空,操作失败,抛出NoSuchElementException
offer添加一个元素如果队列满了,返回false。如果添加成
功,返回 true
poll返回并删除队列的头元素如果队列空,删除失败,返回null
peek返回队列的头元素如果队列空,操作失败,返回null
put添加一个元素如果队列满了,就会阻塞
take返回并删除队列的头元素如果队列是空的,就会阻塞


http://www.ppmy.cn/devtools/166261.html

相关文章

穿梭车与机器人协同作业:构建高效仓储物流系统的关键

在仓储物流领域&#xff0c;效率和准确性至关重要。穿梭车和机器人作为自动化技术的代表&#xff0c;通过协同作业能够显著提升仓储效率&#xff0c;降低成本&#xff0c;构建高效智能的物流系统。 一、穿梭车与机器人的优势 穿梭车: 高效存储与检索: 穿梭车在密集存储系统中…

卡尔曼滤波算法从理论到实践:在STM32中的嵌入式实现

摘要&#xff1a;卡尔曼滤波&#xff08;Kalman Filter&#xff09;是传感器数据融合领域的经典算法&#xff0c;在姿态解算、导航定位等嵌入式场景中广泛应用。本文将从公式推导、代码实现、参数调试三个维度深入解析卡尔曼滤波&#xff0c;并给出基于STM32硬件的完整工程案例…

四种主要的 API 架构风格:RPC、SOAP、REST、GRAPHQL

讨论四种主要的 API 架构风格&#xff0c;比较它们的优缺点&#xff0c;并重点介绍每种情况下最适合的 API 架构风格。 RPCSOAPRESTGRAPHQL 两个单独的应用程序需要中介程序才能相互通信&#xff0c;因此&#xff0c;开发人员经常需要搭建桥梁——也就是应用程序编程接口&…

电力行业中分布式能源管理(Distributed Energy Management System, DEMS)的实现

以下是电力行业中分布式能源管理(Distributed Energy Management System, DEMS)的实现方案,涵盖系统架构、关键技术、核心功能及实施路径,结合典型场景与代码示例: 一、系统架构设计 采用云-边-端三层架构,实现分布式能源的高效协同管理: 1. 终端层(感知层) 设备组…

【数据结构】初识集合框架及背后的数据结构(简单了解)

目录 前言 如何学好数据结构 1. 什么是集合框架 2. 集合框架的重要性 3. 背后所涉及的数据结构以及算法 3.1 什么是数据结构 3.2 容器背后对应的数据结构 3.3 相关java知识 3.4 什么是算法 3.5 基本关系说明&#xff08;重要&#xff0c;简单了解&#xff09; 前言 …

MyBatis-Plus 与 Spring Boot 的最佳实践

在现代 Java 开发中,MyBatis-Plus 和 Spring Boot 的结合已经成为了一种非常流行的技术栈。MyBatis-Plus 是 MyBatis 的增强工具,提供了许多便捷的功能,而 Spring Boot 则简化了 Spring 应用的开发流程。本文将探讨如何将 MyBatis-Plus 与 Spring Boot 进行整合,并分享一些…

transformer模型介绍——大语言模型 LLMBook 学习(二)

1. transformer模型 1.1 注意力机制 **注意力机制&#xff08;Attention Mechanism&#xff09;**在人工智能中的应用&#xff0c;实际上是对人类认知系统中的注意力机制的一种模拟。它主要模仿了人类在处理信息时的选择性注意&#xff08;Selective Attention&#xff09;&a…

WPS的付费功能,这款软件可完美平替

因为作者有工作上的需求加上WPS使用批量提取图片需要会员&#xff0c;所以自己使用cursor制作了一个从excel中提取图片的工具。 支持提取Excel中的浮动图片和根据图片链接来下载图片。 Excel Image Extractor Excel图片提取工具 软件的功能非常强大&#xff0c;支持提取Excel中…