深入理解 Java 阻塞队列:使用场景、原理与性能优化

在并发编程中,线程安全的队列是解决线程间任务传递和调度的关键工具之一。阻塞队列(BlockingQueue)作为一种线程安全的队列,实现了在并发环境下对共享数据的安全访问,广泛应用于生产者-消费者模型、任务调度和多线程计算中。本文将详细介绍阻塞队列的概念、常见实现、线程安全原理及与线程池的结合使用,帮助你全面掌握 Java 中阻塞队列的应用。

1. 什么是阻塞队列

阻塞队列(BlockingQueue)是一个线程安全的队列,它支持在特定条件下对队列的操作进行阻塞。BlockingQueue 接口继承自 Queue,并提供了几个核心方法:take()put()offer()poll(),其中 take()put() 是阻塞操作,能够在队列为空时等待数据,或在队列满时等待空闲空间。

public interface BlockingQueue<E> extends Queue<E> {
    void put(E e) throws InterruptedException;
    E take() throws InterruptedException;
    boolean offer(E e);
    E poll();
    // 其他方法
}

应用场景:

  • 生产者消费者模型:多个生产者线程将任务放入队列,多个消费者线程从队列中取出任务执行,队列的大小决定了系统的缓冲能力。
  • 任务调度:队列可以用来调度和管理任务,保证任务的顺序执行和线程间的协调。

2. 主要的并发队列关系图

Java 提供了多种线程安全的队列,主要可以分为两类:

  • 阻塞队列(BlockingQueue)
  • 非阻塞队列(如 ConcurrentLinkedQueue

这两类队列各自适用于不同的场景,阻塞队列适合于需要控制线程协作的场景,非阻塞队列则适合于高并发、高性能的无阻塞任务处理。

3. 阻塞队列的特点

阻塞队列的最大特点是它的阻塞操作,主要体现在以下两个方法:

  • take():如果队列为空,消费者线程会被阻塞,直到队列中有数据可用。
  • put():如果队列已满,生产者线程会被阻塞,直到队列有空闲空间。

这些方法的阻塞特性使得阻塞队列非常适合于生产者-消费者模型,它能够保证任务的有序执行,并且自动控制线程的执行顺序。

4. 常用方法

常见的 BlockingQueue 方法包括:

  • add():向队列中添加元素,队列已满时抛出异常。
  • remove():移除并返回队列头部的元素,队列为空时抛出异常。
  • offer():向队列中添加元素,队列已满时返回 false
  • poll():移除并返回队列头部的元素,队列为空时返回 null
  • put():向队列中添加元素,队列已满时阻塞当前线程,直到有空间可用。
  • take():从队列中获取并移除元素,队列为空时阻塞当前线程,直到有数据可用。

5. 常见阻塞队列

Java 提供了多种实现了 BlockingQueue 接口的常见阻塞队列,每种队列的实现都具有不同的特点,适用于不同的应用场景。

5.1 ArrayBlockingQueue

ArrayBlockingQueue 是一个有界阻塞队列,内部使用数组存储元素,具有固定的容量,适用于任务数已知且较为稳定的场景。

BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);

5.2 LinkedBlockingQueue

LinkedBlockingQueue 是一个基于链表的阻塞队列,可以设置容量,容量默认值为 Integer.MAX_VALUE。适用于任务量动态变化的场景。

BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(100);

5.3 SynchronousQueue

SynchronousQueue 是一种特殊的阻塞队列,其容量为 0。每次生产任务时,必须有消费者线程来接收该任务,否则生产者会被阻塞。适用于快速传递任务的场景。

BlockingQueue<Integer> queue = new SynchronousQueue<>();

5.4 PriorityBlockingQueue

PriorityBlockingQueue 是一个无界的阻塞队列,支持优先级排序,队列中的元素根据优先级进行排序,适用于需要处理优先级任务的场景。

BlockingQueue<Integer> queue = new PriorityBlockingQueue<>();

5.5 DelayQueue

DelayQueue 是一个支持延时任务的无界阻塞队列,任务可以设置延迟时间,任务到期后才会被消费。适用于定时任务调度的场景。

BlockingQueue<Delayed> queue = new DelayQueue<>();

6. 阻塞和非阻塞队列的并发安全原理

6.1 ArrayBlockingQueue 源码分析

ArrayBlockingQueue 内部使用数组存储元素,使用 ReentrantLockCondition 实现并发控制。put()take() 方法会通过 lock 锁住队列,阻塞操作使用 notFullnotEmpty 条件变量来控制线程的同步。

public void put(E e) throws InterruptedException {
    lock.lockInterruptibly();
    try {
        while (count == items.length)
            notFull.await();
        enqueue(e);
    } finally {
        lock.unlock();
    }
}
  • ReentrantLock:提供了对队列的独占锁,确保线程在操作队列时是互斥的。
  • Condition:通过 notFullnotEmpty 条件变量来控制线程的等待和唤醒。

6.2 非阻塞队列 ConcurrentLinkedQueue

ConcurrentLinkedQueue 是一个无界的非阻塞队列,内部通过 CAS(Compare-And-Swap)机制实现线程安全,适用于高并发场景。它使用 compareAndSwapObject 方法进行原子操作,保证多个线程同时访问队列时不发生冲突。

public boolean offer(E e) {
    final Node<E> newNode = new Node<>(e);
    for (Node<E> t = tail, p = t;;) {
        Node<E> q = p.next;
        if (q == null) {
            if (p.casNext(null, newNode)) {
                if (p != t)
                    casTail(t, newNode);
                return true;
            }
        }
        p = q;
    }
}

7. 线程池与阻塞队列

线程池与阻塞队列常常一起使用,阻塞队列作为线程池的任务队列,用于存储待处理的任务。常见的线程池类型及其与阻塞队列的配合关系如下:

线程池类型阻塞队列类型
FixedThreadPoolLinkedBlockingQueue
SingleThreadExecutorLinkedBlockingQueue
CachedThreadPoolSynchronousQueue
ScheduledThreadPoolDelayWorkQueue
SingleThreadScheduledExecutorDelayedWorkQueue

7.1 LinkedBlockingQueue

适用于 FixedThreadPoolSingleThreadExecutor,由于这两个线程池的线程数固定,任务队列的容量可以设置较大,确保不会因为队列满而拒绝任务。

7.2 SynchronousQueue

适用于 CachedThreadPool,它的容量为 0,每个任务都会立即被执行,因此线程池的线程数可以动态变化。

7.3 DelayWorkQueue

适用于定时任务,如 ScheduledThreadPoolSingleThreadScheduledExecutor,能够根据任务的延迟时间进行调度。

总结

阻塞队列是并发编程中的一个重要工具,它通过线程安全的队列机制,保证了在多线程环境下的数据传递和协调。Java 提供了多种实现方式,如 ArrayBlockingQueueLinkedBlockingQueueSynchronousQueue 等,可以根据不同的业务需求选择合适的阻塞队列类型。掌握阻塞队列的使用和原理,能够帮助你构建更加高效和可靠的并发程序。

🌟 关注我的CSDN博客,收获更多技术干货! 🌟

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络八股整理(一)

计算机网络八股文整理 一&#xff1a;网络模型 1&#xff1a;网络osi模型和tcp/ip模型分别介绍一下 osi模型是国际标准的网络模型&#xff0c;它由七层组成&#xff0c;从上到下分别是&#xff1a;应用层&#xff0c;表示层&#xff0c;会话层&#xff0c;传输层&#xff0c;…

今天你学C++了吗?——C++中的类与对象(第二集)

♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥ ♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥ ♥♥♥我们一起努力成为更好的自己~♥♥♥ ♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥ ♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥ ✨✨✨✨✨✨ 个…

【C++习题】14.滑动窗口_找到字符串中所有字母异位词

文章目录 题目链接&#xff1a;题目描述&#xff1a;解法C 算法代码&#xff1a;图解 题目链接&#xff1a; 438. 找到字符串中所有字母异位词 题目描述&#xff1a; 解法 暴力解法&#xff1a; 字母排序后运用滑动窗口解题。 滑动窗口哈希表&#xff1a; 我们可以优化一下&am…

Spring Boot集成MyBatis-Plus:自定义拦截器实现动态表名切换

Spring Boot集成MyBatis-Plus&#xff1a;自定义拦截器实现动态表名切换 一、引言 介绍动态表名的场景需求&#xff0c;比如多租户系统、分表分库&#xff0c;或者不同业务模块共用一套代码但操作不同表。说明 MyBatis-Plus 默认绑定固定表名的问题。 二、项目配置 1. 集成 M…

深入探索API爬虫工作的技术难点与高效解决思路

在大数据与信息化高速发展的今天&#xff0c;API&#xff08;应用程序编程接口&#xff09;爬虫成为了数据收集与分析的重要工具。然而&#xff0c;API爬虫工作并非一帆风顺&#xff0c;它面临着诸多技术挑战。本文将深入探讨几个API爬虫工作的技术难点&#xff0c;并提出相应的…

css效果

css炫彩流光圆环效果 <!DOCTYPE html> <html><head><meta charset"utf-8" /><title></title><style>*{margin: 0;padding: 0;}body{width: 100%;height: 100vh;}.container{position: relative;width: 100%;height: 100vh…

arm Rk1126 编译Qt工程报错: Could not find qmake spec

首先修改qmake.conf文件&#xff0c;配置好正确的交叉编译工具&#xff1a; 然后执行编译&#xff1a; /opt/Rv1126/Rv1126-盒子代码/rv1126-qt5-sdk/bin/qmake untitled.pro 报错。 原因&#xff1a;中文路径。修改路径为英文路径即可

zabbix监控进程

使用zabbix监控指定的进程&#xff0c;现在主要使用监控一些用java python写的一些微服务模块&#xff0c;我这边用于演示就直接使用nginx服务来演示了 创建监控项 name - 进程名称&#xff08;默认为 ALL PROCESSES);user - 用户名&#xff08;默认为 all users);state - 可能…

php 导出excel 一个单元格 多张图片

public function dumpData(){error_reporting(0); // 禁止错误信息输出ini_set(display_errors, 0); // 不显示错误$limit $this->request->post(limit, 20, intval);$offset $this->request->post(offset, 0, intval);$page floor($offset / $limit) 1 ;$wh…

【C++11】锋芒毕露

(续) 一、可变参数模板 C11支持可变参数模板&#xff0c;也就是说支持可变数量参数的函数模板和类模板&#xff0c;可变数目的参数被称 为参数包&#xff0c;存在两种参数包&#xff1a;模板参数包&#xff0c;表示零或多个模板参数&#xff1b;函数参数包&#xff1a;表示零…

用户管理(MySQL)

目录 1用户管理&#xff08;MySQL&#xff09; 1.1 用户 1.1.1 用户信息 1.1.2 创建用户(后%是可以任意远端登录) 1.1.3 刷新一下 1.1.4 删除用户 1.1.5 修改用户密码 1.2 数据库的权限 1.2.1 登录创建用户 1.2.2给权限 1.2.2.1 把jj数据库中uu表的权限给woaini这个…

Hive离线数仓结构分析

Hive离线数仓结构 首先&#xff0c;在数据源部分&#xff0c;包括源业务库、用户日志、爬虫数据和系统日志&#xff0c;这些都是数据的源头。这些数据通过Sqoop、DataX或 Flume 工具进行提取和导入操作。这些工具负责将不同来源的数据传输到基于 Hive 的离线数据仓库中。 在离线…

Linux——Uboot命令使用

什么是Uboot&#xff1f; 1&#xff09;Uboot是一个裸机程序&#xff0c;比较复杂。类似我们PC机的BIOS程序。 2&#xff09;Uboot就是一个bootloader&#xff0c;作用就是用于启动Linux或者其他系统&#xff0c;Uboot最主要的工作是初始化DDR&#xff0c;因为Linux的运行是运行…

Cannal实现MySQL主从同步环境搭建

大家好&#xff0c;我是袁庭新。 在多数情况下&#xff0c;客户端往往会优先获取缓存中的数据。然而&#xff0c;当缓存数据与数据库中的实际数据存在显著不一致时&#xff0c;可能会导致严重的后果。因此&#xff0c;确保数据库与缓存数据之间的一致性变得至关重要&#xff0c…

C++《二叉搜索树》

在初阶数据结构中我学习了树基础的概念以及了解了顺序结构的二叉树——堆和链式结构二叉树该如何实现&#xff0c;那么接下来我们将进一步的学习二叉树&#xff0c;在此会先后学习到二叉搜索树、AVL树、红黑树&#xff1b;通过这些的学习将让我们更易于理解后面set、map、哈希等…

C++ —— 以真我之名 如飞花般绚丽 - 智能指针

目录 1. RAII和智能指针的设计思路 2. C标准库智能指针的使用 2.1 auto_ptr 2.2 unique_ptr 2.3 简单模拟实现auto_ptr和unique_ptr的核心功能 2.4 shared_ptr 2.4.1 make_shared 2.5 weak_ptr 2.6 shared_ptr的缺陷&#xff1a;循环引用问题 3. shared_ptr 和 unique_…

springboot项目使用maven打包,第三方jar问题

springboot项目使用maven package打包为可执行jar后&#xff0c;第三方jar会被打包进去吗&#xff1f; 答案是肯定的。做了实验如下&#xff1a; 第三方jar的项目结构及jar包结构如下&#xff1a;&#xff08;该第三方jar采用的是maven工程&#xff0c;打包为普通jar&#xf…

第六届智能控制、测量与信号处理国际学术会议 (ICMSP 2024)

重要信息 2024年11月29日-12月1日 中国陕西西安石油大学雁塔校区 大会官网&#xff1a;www.icmsp.net 大会简介 第六届智能控制、测量与信号处理国际学术会议&#xff08;ICMSP 2024&#xff09;由西安石油大学、中海油田服务股份有限公司、浙江水利水电学院与中国石油装备…

设计LRU缓存

LRU缓存 LRU缓存的实现思路LRU缓存的操作C11 STL实现LRU缓存自行设计双向链表 哈希表 LRU&#xff08;Least Recently Used&#xff0c;最近最少使用&#xff09;缓存是一种常见的缓存淘汰算法&#xff0c;其基本思想是&#xff1a;当缓存空间已满时&#xff0c;移除最近最少使…

跨平台应用开发框架(1)----Qt(组件篇)

目录 1.Qt 1.Qt 的主要特点 2.Qt的使用场景 3.Qt的版本 2.QtSDK 1.Qt SDK 的组成部分 2.安装 Qt SDK 3.Qt SDK 的优势 3.Qt初识 1.快速上手 widget.cpp mian.cpp widget.h Helloworld.pro 2.对象树 3.坐标系 4.信号和槽 1. 信号和槽的基本概念 2. 信号和槽的…