news 2026/6/24 21:19:40

【阻塞队列的等待唤醒机制】

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【阻塞队列的等待唤醒机制】

阻塞队列的等待唤醒机制

  • Java阻塞队列的等待唤醒机制详解及自定义实现
    • 1. 阻塞队列的基本概念
    • 2. ArrayBlockingQueue源码剖析(等待唤醒机制)
      • 核心字段
    • 3. 等待唤醒机制的两种实现方式
    • 4. 自定义一个简单阻塞队列
    • 5. 常见面试延伸问题
  • 总结

Java阻塞队列的等待唤醒机制详解及自定义实现

今天来分享一个经典的多线程话题:阻塞队列(BlockingQueue)的等待唤醒机制

  • 在多线程编程中,生产者-消费者模式是最常见的场景之一。而阻塞队列正是解决这个模式的核心工具。它能在队列满时阻塞生产者、在队列空时阻塞消费者,从而实现线程间安全高效的协作。

本文将从以下几个方面进行讲解:

  1. 阻塞队列的基本概念和作用
  2. Java并发包中阻塞队列的实现原理(以ArrayBlockingQueue为例)
  3. 等待唤醒机制的核心:wait()、notify() 与 Lock + Condition
  4. 手把手自定义一个简单阻塞队列
  5. 常见面试题延伸

1. 阻塞队列的基本概念

阻塞队列(BlockingQueue)是java.util.concurrent包下的接口,它的典型实现有:

  • ArrayBlockingQueue:基于数组的有界阻塞队列
  • LinkedBlockingQueue:基于链表的有界/无界阻塞队列
  • PriorityBlockingQueue:支持优先级的无界阻塞队列
  • SynchronousQueue:不存储元素的阻塞队列(容量为0)

阻塞队列的核心操作:

  • put(e):向队列尾部添加元素,如果队列已满,则阻塞当前线程,直到有空间
  • take():从队列头部移除元素,如果队列为空,则阻塞当前线程,直到有元素

这种“阻塞”行为,正是通过等待唤醒机制实现的。

2. ArrayBlockingQueue源码剖析(等待唤醒机制)

我们以最常用的ArrayBlockingQueue为例,看看它是如何实现阻塞的。

核心字段

final ReentrantLock lock;// 单一锁控制所有操作privatefinal Condition notEmpty;// 消费者等待条件(队列不空)privatefinal Condition notFull;// 生产者等待条件(队列不满)privatefinal Object[]items;// 底层数组int count;// 当前元素数量put操作(入队) Javapublicvoidput(Ee)throws InterruptedException{Objects.requireNonNull(e);final ReentrantLock lock=this.lock;lock.lockInterruptibly();try{while(count==items.length){// 队列满时notFull.await();// 阻塞生产者,释放锁}enqueue(e);// 实际入队if(++count==items.length)// 如果入队后变满,无需唤醒生产者return;notEmpty.signal();// 唤醒一个等待的消费者}finally{lock.unlock();}}take操作(出队) JavapublicEtake()throws InterruptedException{final ReentrantLock lock=this.lock;lock.lockInterruptibly();try{while(count==0){// 队列空时notEmpty.await();// 阻塞消费者,释放锁}Eitem=dequeue();// 实际出队if(--count==0)// 如果出队后变空,无需唤醒消费者returnitem;notFull.signal();// 唤醒一个等待的生产者}finally{lock.unlock();}}

关键点总结:

使用单一ReentrantLock保证线程安全
通过两个Condition(notEmpty、notFull)精确控制唤醒:
生产者只唤醒消费者(notEmpty.signal())
消费者只唤醒生产者(notFull.signal())
await() 会释放锁,让其他线程有机会操作队列
signal() 只唤醒一个线程,避免惊群效应
这比使用synchronized + wait/notify更高效、更灵活(可以有多个Condition)。

3. 等待唤醒机制的两种实现方式

方式一:synchronized + wait/notify(传统方式)
wait():当前线程释放锁并进入WAITING状态
notify():随机唤醒一个等待线程
notifyAll():唤醒所有等待线程(容易造成惊群)
方式二:Lock + Condition(推荐方式)
condition.await():类似wait()
condition.signal():类似notify()
condition.signalAll():类似notifyAll()
优势:一个Lock可以创建多个Condition,实现精确唤醒
阻塞队列普遍采用第二种方式。

4. 自定义一个简单阻塞队列

我们来手写一个基于数组的有界阻塞队列,使用synchronized + wait/notify实现(便于理解):

Javaimportjava.util.concurrent.atomic.AtomicInteger;publicclassMyBlockingQueue<E>{privatefinal Object[]items;privateint takeIndex;// 消费指针privateint putIndex;// 生产指针privatefinal AtomicInteger count=newAtomicInteger(0);privatefinal int capacity;publicMyBlockingQueue(int capacity){this.capacity=capacity;this.items=newObject[capacity];}publicsynchronizedvoidput(Ee)throws InterruptedException{while(count.get()==capacity){// 队列满wait();// 释放锁,阻塞生产者}items[putIndex]=e;if(++putIndex==capacity){putIndex=0;}count.incrementAndGet();notifyAll();// 唤醒消费者(这里用notifyAll避免遗漏)}publicsynchronizedEtake()throws InterruptedException{while(count.get()==0){// 队列空wait();// 释放锁,阻塞消费者}@SuppressWarnings("unchecked")Eitem=(E)items[takeIndex];items[takeIndex]=null;// help GCif(++takeIndex==capacity){takeIndex=0;}count.decrementAndGet();notifyAll();// 唤醒生产者returnitem;}}

测试代码:

JavapublicclassTest{publicstaticvoidmain(String[]args){MyBlockingQueue<Integer>queue=newMyBlockingQueue<>(5);// 生产者newThread(()->{for(int i=1;i<=10;i++){try{queue.put(i);System.out.println("生产: "+i);}catch(InterruptedException e){Thread.currentThread().interrupt();}}}).start();// 消费者newThread(()->{for(int i=1;i<=10;i++){try{Integer val=queue.take();System.out.println("消费: "+val);}catch(InterruptedException e){Thread.currentThread().interrupt();}}}).start();}}

运行结果会看到生产者和消费者交替进行,当队列满/空时自动阻塞。

注意:实际生产中推荐使用Lock + Condition,因为notifyAll()可能导致所有线程都被唤醒后再竞争锁,性能较低。

5. 常见面试延伸问题

为什么不直接用notify而是用notifyAll?
在环形队列中,生产者和消费者都在同一个锁上等待,使用notify()可能唤醒同类型线程,导致继续等待(死等)。notifyAll()更安全。
ArrayBlockingQueue为什么用单一锁而不是读写分离?
为了实现强一致性(入队立即对出队可见),且数组实现下读写锁分离收益不大。
LinkedBlockingQueue为什么用两把锁?
链表头尾操作相对独立,使用takeLock和putLock分离,提高并发度。

总结

  • 阻塞队列的等待唤醒机制本质是:在临界区判断条件不满足时,释放锁并等待;条件满足后唤醒等待线程。

  • Java并发包通过ReentrantLock + Condition实现了高效精确的阻塞队列,是我们日常开发中线程安全协作的利器。

希望本文对你理解阻塞队列的底层原理有所帮助!如果有问题欢迎留言讨论~

点赞 + 收藏 + 关注,三连走一走~

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/25 1:00:31

基于YOLO11-seg的白蚁种类智能识别与分类系统——利用深度学习技术实现白蚁图像分割与多类别精准识别

该数据集为白蚁种类识别与分类任务提供了丰富的视觉资源&#xff0c;共包含3153张经过预处理的图像&#xff0c;所有图像均以YOLOv8格式进行了标注。数据集在预处理阶段采用了自动方向调整&#xff08;去除EXIF方向信息&#xff09;并将所有图像统一缩放至640640像素的尺寸&…

作者头像 李华
网站建设 2026/6/25 11:04:35

AI写论文“终极PK”:宏智树AI凭啥成2025届毕业生的“隐形导师”

官网直达&#xff1a;www.hzsxueshu.com 毕业季的“论文战场”上&#xff0c;有人为选题愁到脱发&#xff0c;有人为文献查重熬红双眼&#xff0c;更有人因格式混乱被导师“连环暴击”。当通用AI还在生成“车轱辘话”时&#xff0c;一款名为宏智树AI的论文助手悄然杀出——它不…

作者头像 李华
网站建设 2026/6/24 21:28:31

AI写论文哪个软件最好?与宏智树AI共舞,毕业从“困局”到“胜局”

宏智树AI&#xff0c;远不止于论文写作。它是一款专为学术旅程设计的智能伙伴&#xff0c;旨在与你并肩完成从灵感到终稿的全过程。从第一缕灵感的落地——一份结构清晰的开题报告&#xff0c;到广纳百家之言的文献综述&#xff0c;再到主体章节的精心构筑&#xff0c;乃至对学…

作者头像 李华
网站建设 2026/6/24 10:30:00

Symfony 8 Monolog配置避坑指南:5个常见错误及修复方案

第一章&#xff1a;Symfony 8 日志系统概览Symfony 8 的日志系统建立在强大的 Monolog 库之上&#xff0c;为开发者提供灵活、可扩展的日志记录机制。无论是在开发环境调试问题&#xff0c;还是在生产环境中监控应用行为&#xff0c;Symfony 都能通过配置将不同级别的日志输出到…

作者头像 李华
网站建设 2026/6/23 18:41:45

模型融合后如何验证?R和Python输出差异的真相,90%的人都忽略了

第一章&#xff1a;R-Python 模型融合的结果验证在跨语言建模场景中&#xff0c;R 与 Python 的模型融合已成为提升预测性能的重要手段。通过将 R 中擅长的统计分析模型与 Python 在机器学习框架上的优势结合&#xff0c;可以构建更稳健的集成系统。然而&#xff0c;融合后的结…

作者头像 李华