Java中的阻塞队列

阻塞队列是什么?

一、阻塞队列的核心概念与特性

1.1 阻塞队列是什么?

简单来说,阻塞队列是一种特殊的队列,它具备普通队列先进先出(FIFO)的特性,同时还支持两个额外的重要操作

  1. 当队列为空时,获取元素的操作会被阻塞,直到队列中有元素可用;
  2. 当队列已满时,插入元素的操作会被阻塞,直到队列中有空间可用 。

这种特性使得阻塞队列在生产者 - 消费者模型、线程池等场景中,成为实现线程安全数据传输的理想选择。

例如,在经典的生产者 - 消费者模型里,有多个生产者线程不断生成数据并尝试放入队列,同时有多个消费者线程从队列中取出数据进行处理。如果使用普通队列,在多线程并发访问时,很容易出现数据竞争、线程安全等问题,比如生产者在队列已满时继续添加数据导致数据溢出,或者消费者在队列空时尝试获取数据引发空指针异常等。而阻塞队列则可以很好地解决这些问题,当队列满时,生产者线程会被阻塞,等待队列有空间时再进行插入操作;当队列空时,消费者线程会被阻塞,直到有新的数据被生产者放入队列。

阻塞队列通常是线程安全的,这意味着多个线程可以同时访问阻塞队列,而无需开发者手动添加额外的同步措施,其内部实现通常使用了锁和条件变量等同步机制来确保数据的一致性和线程安全。此外,阻塞队列还支持多种操作,如插入元素、获取元素、查看队首元素等,这些丰富的操作方法能够满足不同的应用需求,使得阻塞队列在多线程编程中有着广泛的应用场景,例如任务调度、消息传递系统、线程池等

1.2 核心特性解析

1.2.1 阻塞与唤醒机制

阻塞队列基于条件变量(Condition)和锁机制实现阻塞与唤醒。当线程尝试从空队列取元素或向满队列添加元素时,会释放锁并进入等待状态;而当队列状态改变(如其他线程添加或移除元素)时,相应的等待线程会被唤醒,重新尝试操作。

1.2.2 线程安全

所有阻塞队列都实现了java.util.concurrent.BlockingQueue接口,并通过内部锁(如ReentrantLock)和原子操作,确保多线程环境下数据操作的原子性和一致性,避免数据竞争和线程安全问题。

二、Java 中的 7 种阻塞队列详解

2.1 ArrayBlockingQueue:数组实现的有界队列

  • 数据结构:基于固定大小的数组实现,初始化时需指定容量。
  • 特点
    • 有界性:队列容量固定,插入元素超过容量时会阻塞。
    • 公平性:支持公平与非公平模式(默认非公平)。公平模式下,线程获取锁的顺序遵循 FIFO 原则,但会降低吞吐量。
  • 适用场景:适用于已知最大容量需求,且对内存占用敏感的场景,如内存有限的消息缓冲队列。

2.2 LinkedBlockingQueue:链表实现的有界队列

  • 数据结构:基于单向链表实现,默认容量为Integer.MAX_VALUE,也可指定容量。
  • 特点
    • 高吞吐量:读写操作分别使用两把锁(takeLock和putLock),减少锁竞争,提升并发性能。
    • 动态扩容:链表结构允许队列在一定范围内动态增长。
  • 适用场景:适用于生产者和消费者速度差异较大,需要缓冲大量数据的场景,如日志处理队列。

2.3 PriorityBlockingQueue:支持优先级的无界队列

  • 数据结构:基于 ** 堆(通常为二叉堆)** 实现,元素需实现Comparable接口或通过Comparator指定排序规则。
  • 特点
    • 优先级排序:每次取出的元素为队列中优先级最高的元素。
    • 无界性:队列容量可动态增长,但需注意内存溢出风险。
  • 适用场景:适用于任务调度、资源分配等需要按优先级处理数据的场景,如高优先级任务优先执行的线程池。

2.4 DelayQueue:基于优先级的延迟队列

  • 数据结构:基于PriorityBlockingQueue实现,元素需实现Delayed接口,包含getDelay和compareTo方法。
  • 特点
    • 延迟特性:元素只有在getDelay方法返回的时间到期后,才能被取出。
    • 无界性:队列容量可动态增长。
  • 适用场景:适用于定时任务、缓存过期清理、消息延迟发送等场景,如订单超时取消任务。

2.5 SynchronousQueue:不存储元素的队列

  • 数据结构:内部不存储任何元素,生产者线程插入元素时,必须等待消费者线程取走元素。
  • 特点
    • 直接传递:数据直接从生产者传递给消费者,无缓冲功能。
    • 高性能:减少内存占用和线程上下文切换开销。
  • 适用场景:适用于生产者和消费者处理速度相近,需要高效传递数据的场景,如线程池的直接提交策略。

2.6 LinkedTransferQueue:链表结构的无界 TransferQueue

  • 数据结构:基于链表实现,继承自TransferQueue接口。
  • 特点
    • 高效传递:transfer方法允许生产者直接将元素传递给等待的消费者,避免入队操作。
    • 无界性:队列容量可动态增长。
  • 适用场景:适用于高并发场景下的快速数据传递,如响应式编程中的事件流处理。

2.7 LinkedBlockingDeque:链表结构的双向阻塞队列

  • 数据结构:基于双向链表实现,支持从队列两端进行插入和移除操作。
  • 特点
    • 双端操作:提供putFirst、putLast、takeFirst、takeLast等方法,灵活处理数据。
    • 无界性:默认容量为Integer.MAX_VALUE,也可指定容量。
  • 适用场景:适用于需要双向处理数据的场景,如线程池中的任务窃取机制。

三、阻塞队列的实现原理剖析

3.1 以 ArrayBlockingQueue 为例解析源码

3.1.1 数据结构与成员变量
public class ArrayBlockingQueue<E> extends AbstractQueue<E>implements BlockingQueue<E>, java.io.Serializable {// 元素存储数组final Object[] items;// 队列头部索引int takeIndex;// 队列尾部索引int putIndex;// 当前元素数量int count;// 同步锁final ReentrantLock lock;// 非空条件:当队列有元素时通知读取线程private final Condition notEmpty;// 非满条件:当队列有空位时通知写入线程private final Condition notFull;
}
3.1.2 put 方法实现
public void put(E e) throws InterruptedException {checkNotNull(e);final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == items.length) {notFull.await(); // 队列已满,线程等待}enqueue(e);} finally {lock.unlock();}
}private void enqueue(E x) {final Object[] items = this.items;items[putIndex] = x;if (++putIndex == items.length) {putIndex = 0;}count++;notEmpty.signal(); // 唤醒取元素线程
}
3.1.3 take 方法实现
public E take() throws InterruptedException {final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == 0) {notEmpty.await(); // 队列为空时线程等待}return dequeue();} finally {lock.unlock();}
}private E dequeue() {final Object[] items = this.items;@SuppressWarnings("unchecked")E element = (E) items[takeIndex];items[takeIndex] = null;if (++takeIndex == items.length) {takeIndex = 0;}count--;notFull.signal(); // 通知可添加元素的线程return element;
}

3.2 通用实现思路总结

  1. 数据结构:根据队列特性选择数组、链表或堆等数据结构。
  2. 锁机制:使用ReentrantLock或ConcurrentHashMap等实现线程安全。
  3. 条件变量:通过Condition对象实现线程的阻塞与唤醒,基于等待 - 通知模式(Wait - Notify Pattern)。
  4. 边界处理:对队列满、队列空等边界条件进行严密判断和处理。

四、阻塞队列的典型应用场景

4.1 生产者 - 消费者模型

阻塞队列天然适配生产者 - 消费者模型,生产者线程将数据放入队列,消费者线程从队列取出数据。队列的阻塞特性自动处理了数据生产与消费的速度差异,避免了数据丢失或线程忙等待。

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;class Producer implements Runnable {private final BlockingQueue<Integer> queue;public Producer(BlockingQueue<Integer> queue) {this.queue = queue;}@Overridepublic void run() {try {for (int i = 1; i <= 10; i++) {queue.put(i);System.out.println("Produced: " + i);Thread.sleep(1000);}} catch (InterruptedException e) {Thread.currentThread().interrupt();}}
}class Consumer implements Runnable {private final BlockingQueue<Integer> queue;public Consumer(BlockingQueue<Integer> queue) {this.queue = queue;}@Overridepublic void run() {try {while (true) {Integer item = queue.take();System.out.println("Consumed: " + item);}} catch (InterruptedException e) {Thread.currentThread().interrupt();}}
}public class Main {public static void main(String[] args) {BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();Thread producerThread = new Thread(new Producer(queue));Thread consumerThread = new Thread(new Consumer(queue));producerThread.start();consumerThread.start();}
}

4.2 线程池任务队列

Java 线程池(如ThreadPoolExecutor)通过阻塞队列管理待执行任务。当线程池中的线程都处于忙碌状态时,新提交的任务会被放入阻塞队列中等待执行。不同类型的阻塞队列(如ArrayBlockingQueue、LinkedBlockingQueue)可用于调整线程池的工作特性,如任务排队策略、最大任务容量等。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class ThreadPoolExample {public static void main(String[] args) {// Initialize thread pool with core settingsThreadPoolExecutor executor = new ThreadPoolExecutor(2,  // Core pool size4,  // Maximum pool size60, // Keep-alive timeTimeUnit.SECONDS,new ArrayBlockingQueue<>(10) // Bounded task queue);// Submit 20 tasks to the thread poolfor (int i = 0; i < 20; i++) {executor.submit(() -> {try {Thread.sleep(2000);System.out.println(Thread.currentThread().getName() + " executing task");} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}executor.shutdown();}
}

五、总结与展望

本文全面解析了 Java 阻塞队列的核心概念、7 种常见队列的特性、实现原理及典型应用场景。掌握阻塞队列的使用,能显著提升多线程程序的稳定性和性能。随着 Java 并发编程技术的不断发展,阻塞队列也将在高并发、分布式等领域发挥更重要的作用。未来,开发者可以进一步探索阻塞队列与其他并发工具(如CompletableFuture、Fork/Join框架)的结合应用,解锁更多高效的并发编程解决方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/news/908250.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

v1.0.1版本更新·2025年5月22日发布-优雅草星云物联网AI智控系统

v1.0.1版本更新2025年5月22日发布-优雅草星云物联网AI智控系统 开源地址 星云智控官网&#xff1a; 优雅草星云物联网AI智控软件-移动端vue: 优雅草星云物联网AI智控软件-移动端vue 星云智控PC端开源&#xff1a; 优雅草星云物联网AI智控软件-PC端vue: 优雅草星云物联网AI…

Java-IO流之转换流详解

Java-IO流之转换流详解 一、转换流概述1.1 什么是转换流1.2 转换流的作用1.3 转换流的位置 二、InputStreamReader详解2.1 基本概念2.2 构造函数2.3 核心方法2.4 使用示例&#xff1a;读取不同编码的文件 三、OutputStreamWriter详解3.1 基本概念3.2 构造函数3.3 核心方法3.4 使…

android lifeCycleOwner生命周期

一 Fragment中 viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) 什么时候执行&#xff1f; 让我分析一下相关问题&#xff1a; 关于 onPause 时的数据更新: viewLifecycleOwner.lifecycleScope.launch {viewLifecycleOwner.repeatOnLifecycle(Lifecycle.Sta…

Liunx进程替换

文章目录 1.进程替换2.替换过程3.替换函数exec3.1命名解释 4.细说6个exe函数execl函数execvexeclp、execvpexecle、execve 1.进程替换 fork&#xff08;&#xff09;函数在创建子进程后&#xff0c;子进程如果想要执行一个新的程序&#xff0c;就可以使用进程的程序替换来完成…

【华为云Astro-服务编排】服务编排中图元的使用与配置

目录 子服务编排图元 子服务编排图元的作用 如何使用子服务编排图元 脚本图元 脚本图元的作用 如何使用脚本图元 记录创建图元 记录创建图元的作用 如何使用记录创建图元 记录删除图元 记录删除图元的作用 如何使用记录删除图元 记录查询图元 记录查询图元的作用…

SQL Server相关的sql语句

目录 一、数据定义语言&#xff08;DDL&#xff09;1. 创建数据库2. 修改数据库3. 删除数据库4. 创建表5. 修改表结构6. 删除表 二、数据操作语言&#xff08;DML&#xff09;1. 插入数据2. 更新数据3. 删除数据 三、数据查询语言&#xff08;DQL&#xff09;1. 基础查询2. 去重…

【Hot 100】55. 跳跃游戏

目录 引言跳跃游戏我的解题 &#x1f64b;‍♂️ 作者&#xff1a;海码007&#x1f4dc; 专栏&#xff1a;算法专栏&#x1f4a5; 标题&#xff1a;【Hot 100】55. 跳跃游戏❣️ 寄语&#xff1a;书到用时方恨少&#xff0c;事非经过不知难&#xff01; 引言 跳跃游戏 &#x…

基于51单片机的车内防窒息检测报警系统

目录 具体实现功能 设计介绍 资料内容 全部内容 资料获取 具体实现功能 具体实现功能&#xff1a; &#xff08;1&#xff09;检测车内温度及二氧化碳浓度并用lcd1602实时显示。 &#xff08;2&#xff09;当人体红外传感器检测到车内有人&#xff0c;且温度或二氧化碳浓度…

关于智能体API参考接口

关于智能体在Flask的源码&#xff1a;请求体(在payload里的是请求体)、请求头&#xff08;在headers里的i局势请求头&#xff09;。 我的例子&#xff1a; 我的疑问&#xff1a;为什么没按Coze官方API文档格式&#xff0c;在Apifox里发POST请求却能收到回复&#xff1f; 1. 你…

Excel 批量下载PDF、批量下载考勤图片——仙盟创梦IDE

在办公场景中&#xff0c;借助应用软件实现 Excel 批量处理考勤图片、电子文档与 PDF&#xff0c;具有诸多显著优势。 从考勤图片处理来看&#xff0c;通过 Excel 批量操作&#xff0c;能快速提取图片中的考勤信息&#xff0c;如员工打卡时间、面部识别数据等&#xff0c;节省…

Apache Doris + MCP:Agent 时代的实时数据分析底座

一、Apache Doris&#xff1a;面向 Agent 时代的智能数据平台 当我们谈论 2025 年时&#xff0c;业界普遍认为这将是"Agent 革命年"&#xff08;Agentic Revolution&#xff09;的开端。与传统的人机交互模式不同&#xff0c;AI Agent 作为一个全新的"用户角色…

能不能用string接收数据库的datetime类型字段

在Java中使用String类型通过MyBatis接收MySQL的datetime类型字段时&#xff0c;​可以正常工作&#xff0c;但需注意格式和潜在问题。以下是关键点&#xff1a; 1. ​直接转换是可行的​ MySQL的datetime字段&#xff08;如 2023-10-05 12:34:56&#xff09;会被MyBatis自动转…

【Python训练营打卡】day44 @浙大疏锦行

DAY 44 预训练模型 知识点回顾&#xff1a; 1. 预训练的概念 2. 常见的分类预训练模型 3. 图像预训练模型的发展史 4. 预训练的策略 5. 预训练代码实战&#xff1a;resnet18 作业&#xff1a; 1. 尝试在cifar10对比如下其他的预训练模型&#xff0c;观察差异&#xff0c;…

MySQL中关于事务和锁的常见执行命令整理包括版本区别

MySQL中关于事务和锁的常见执行命令实例整理&#xff0c;并标注了不同版本下的区别&#xff08;如MySQL 8.0与旧版本的差异&#xff09;&#xff1a; 一、事务相关命令 1. 事务控制 命令描述版本差异START TRANSACTION; 或 BEGIN;显式开启事务通用语法&#xff0c;无版本差异…

PyTorch-Transforms的使用(二)

对图像进行处理 安装open cv ctrlP 看用法 ToTensor的使用 常见的Transforms 归一化的图片 两个长度为三的数组&#xff0c;分别表示三个通道的平均值和标准差 Resize&#xff08;&#xff09; Compose&#xff08;&#xff09; 合并执行功能&#xff0c;输入进去一个列表&a…

vscode实用配置

前端开发安装插件&#xff1a; 1.可以更好看的显示文件图标 2.用户快速打开文件 使用步骤&#xff1a;在html文件下右键点击 open with live server 即可 刷力扣&#xff1a; 安装这个插件 还需要安装node.js即可

Day130 | 灵神 | 回溯算法 | 子集型 电话号码的字母组合

Day130 | 灵神 | 回溯算法 | 子集型 电话号码的字母组合 17.电话号码的字母组合 17. 电话号码的字母组合 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者用index代替i&#xff0c;这里的index其实就是digits数组的下标 按照灵神的回溯三问&#xff0c;那就…

深入理解JavaScript设计模式之闭包与高阶函数

前言小序 一场失败面试 2023年的某一天&#xff0c;一场让我印象深刻的面试&#xff1a; 面试官&#xff1a; “你了解闭包吗&#xff1f;请说一下你对闭包的理解。” 我自信满满地答道&#xff1a; “闭包就是函数里面套函数&#xff0c;里面的函数可以访问外部函数的变量。…

使用 Spring Boot 3.3 和 JdbcTemplate 操作 MySQL 数据库

在现代的 Java 应用开发中&#xff0c;Spring Boot 提供了强大的工具来简化数据库操作。JdbcTemplate 是 Spring 提供的一个核心类&#xff0c;用于简化 JDBC 操作&#xff0c;减少样板代码。本文将介绍如何在 Spring Boot 3.3 项目中使用 JdbcTemplate 来操作 MySQL 数据库&am…

如何做好一份技术文档?(下篇)

如何做好一份技术文档&#xff1f;&#xff08;下篇&#xff09; 下篇&#xff1a;文档体验的极致优化 ——从可用性到愉悦性的跨越 文档用户体验地图 新手路径 专家路径 [安装] → [配置] → [示例] [API] → [参数] → [源码] │ ▲ …