首页 > 编程语言 > Java 中的阻塞队列

Java 中的阻塞队列

时间:2024-09-07 12:51:53浏览次数:8  
标签:Java 消费者 队列 轮询 阻塞 queue 生产者 线程

1、线程间通信

线程间通信是指多个线程对共享资源的操作和协调。在生产者-消费者模型中,生产者和消费者是不同种类的线程,他们对同一个资源(如队列)进行操作。生产者负责向队列中插入数据,消费者负责从队列中取出数据。


主要挑战在于如何在资源达到上限时让生产者等待,而在资源达到下限时让消费者等待。线程间的这种相互调度,就是线程间通信。


以现实生活为例。消费者和生产者就像两个线程,原本做着各自的事情,厂家管自己生产,消费者管自己买,一般情况下彼此互不影响。900 240




但当物资到达某个临界点时,就需要根据供需关系适当作出调整。比如,当厂家做了一大堆东西,产能过剩时,应该暂停生产,扩大宣传,让消费者过来消费。




同理,当消费者发现某个热销商品售罄,应该提醒厂家尽快生产。




在上面的案例中,生产者和消费者是不同种类的线程,一个负责存入,另一个负责取出,且它们操作的是同一个资源。但最难的部分在于:资源到达上限时,生产者等待,消费者消费;资源达到下限时,生产者生产,消费者等待。


我们可以发现,原本互不打扰的两个线程之间开始了 “沟通”:


生产者:做的商品太多了,应该扩大宣传,让大家来买。

消费者:都卖完啦,应当提醒商家尽快补货。

这种线程间的相互调度,也就是线程间通信。


2、线程间通信的实现

实现线程间通信的方式有多种:


轮询:生产者和消费者线程通过循环不断检查队列的状态。这种方法简单,但会消耗大量 CPU 资源,且无法保证原子性。

等待唤醒机制(wait/notify):通过 wait 和 notify 机制,线程可以在队列为空或满时阻塞自己,当状态改变时由其他线程唤醒。synchronized 保证了线程的原子性,但 notify 可能导致线程竞争不均。

等待唤醒机制(Condition):使用ReentrantLock和Condition实现等待唤醒机制,可以更加精确地控制线程的阻塞和唤醒。通过创建不同的Condition实例,可以分别管理生产者和消费者的等待状态,避免了notify的随机唤醒问题。

2.1、轮询

设计理念:生产者和消费者线程通过循环不断检查队列的状态,队列为空时生产者才可插入数据,队列不为空时消费者才能取出数据,否则一律 sleep 等待。




代码实现:


import java.util.LinkedList;

import java.util.concurrent.TimeUnit;


/**

* 自定义阻塞队列实现:轮询版本

*

* @param <T> 队列中存储的元素类型

*/

public class WhileQueue<T> {

   // 用来存储元素的容器

   private final LinkedList<T> queue = new LinkedList<>();

   // 队列的最大容量

   private final int MAX_SIZE = 1;


   /**

    * 将元素添加到队列中

    *

    * @param resource 要插入的元素

    * @throws InterruptedException 如果当前线程被中断

    */

   public void put(T resource) throws InterruptedException {

       // 如果队列满了,生产者线程将进入轮询等待状态

       while (queue.size() >= MAX_SIZE) {

           System.out.println("生产者:队列已满,无法插入...");

           TimeUnit.MILLISECONDS.sleep(1000); // 线程等待1秒钟再重试

       }

       // 插入元素到队列的前面

       System.out.println("生产者:插入" + resource + "!!!");

       queue.addFirst(resource);

   }


   /**

    * 从队列中取出元素

    *

    * @throws InterruptedException 如果当前线程被中断

    */

   public void take() throws InterruptedException {

       // 如果队列为空,消费者线程将进入轮询等待状态

       while (queue.size() <= 0) {

           System.out.println("消费者:队列为空,无法取出...");

           TimeUnit.MILLISECONDS.sleep(1000); // 线程等待1秒钟再重试

       }

       // 从队列的末尾取出元素

       System.out.println("消费者:取出消息!!!");

       queue.removeLast();

       TimeUnit.MILLISECONDS.sleep(5000); // 模拟消费操作需要时间

   }

}


测试:


/**

* 测试类:创建生产者和消费者线程来测试WhileQueue的功能

*/

public class Test {

   public static void main(String[] args) {

       // 创建一个WhileQueue实例

       WhileQueue<String> queue = new WhileQueue<>();


       // 创建并启动生产者线程

       new Thread(new Runnable() {

           @Override

           public void run() {

               for (int i = 0; i < 100; i++) {

                   try {

                       queue.put("消息" + i); // 插入消息到队列

                   } catch (InterruptedException e) {

                       e.printStackTrace(); // 捕获并打印中断异常

                   }

               }

           }

       }).start();


       // 创建并启动消费者线程

       new Thread(new Runnable() {

           @Override

           public void run() {

               for (int i = 0; i < 100; i++) {

                   try {

                       queue.take(); // 从队列中取出消息

                   } catch (InterruptedException e) {

                       e.printStackTrace(); // 捕获并打印中断异常

                   }

               }

           }

       }).start();

   }

}


由于设定了队列最多只能存1个消息,所以只有当队列为空时,生产者才能插入数据。这是最简单的线程间通信:多个线程不断轮询共享资源,通过共享资源的状态判断自己下一步该做什么。


但上面的实现方式存在一些缺点:


轮询的方式太耗费 CPU 资源,如果线程过多,比如几百上千个线程同时在那轮询,会给 CPU 带来较大负担

无法保证原子性(代码里没有演示,但理论上确实如此,如果生产者的操作非原子性,消费者极可能获取到脏数据)


标签:Java,消费者,队列,轮询,阻塞,queue,生产者,线程
From: https://blog.51cto.com/u_16265692/11944691

相关文章

  • Java计算机毕业设计校园外卖点餐平台app(开题报告+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容进度安排:第一阶段: 熟悉工具,查阅相关资料(1周)第二阶段:分析阶段,确定系统功能及性能等需求(3周)第三阶段:设计阶段,按照需求分析结果,进行系统概要设计及详细设计(3......
  • Java计算机毕业设计携手助学助学交流平台(开题报告+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景在当今社会,教育资源的不均衡分配依然是制约教育公平与质量提升的关键因素之一。随着信息技术的飞速发展,互联网平台为教育资源的共享与优化配置提供了......
  • Java计算机毕业设计医学图像管理平台(开题报告+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着医疗技术的飞速发展,医学图像已成为临床诊断和治疗不可或缺的重要工具。从X光片、CT扫描到MRI图像,这些高精度、高信息量的图像数据不仅为医生提供......
  • Java计算机毕业设计校园台球厅的管理系统(开题报告+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着高校文化的日益丰富与多元化,学生们对于休闲娱乐的需求也日益增长。台球作为一项集竞技性、娱乐性和社交性于一体的运动,深受广大学生群体的喜爱。......
  • Java计算机毕业设计疫苗接种管理系统(开题报告+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着全球公共卫生事件的频发,疫苗接种作为预防和控制传染病的关键手段,其重要性日益凸显。然而,传统的手工管理疫苗接种流程存在效率低下、信息易错、难......
  • Java计算机毕业设计眼镜店管理系统(开题报告+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景:随着消费者健康意识的提升及个性化需求的增长,眼镜行业正经历着前所未有的变革。传统眼镜店的管理方式往往依赖于手工记录与纸质文档,这不仅效率低下,且......
  • Java计算机毕业设计协同过滤图书(开题报告+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景在信息爆炸的时代,图书资源浩如烟海,读者在浩瀚的书海中寻找自己感兴趣的书籍往往费时费力。传统的图书推荐方式依赖于编辑推荐、畅销书榜单或读者主动......
  • Javaweb-DQL-条件查询
    select*fromstuwhereage>20;--1select*fromstuwhereage>=20;--2select*fromstuwhereage>=20andage<=30;--3select*fromstuwherehire_dateBETWEEN'1998-09-01'and'1999-09-01';--4select*fromstuwhere......
  • Javaweb-DQL-条件查询
    select*fromstuwhereage>20;--1select*fromstuwhereage>=20;--2select*fromstuwhereage>=20andage<=30;--3select*fromstuwherehire_dateBETWEEN'1998-09-01'and'1999-09-01';--4select*fromstuwhere......
  • Java 实现下一页功能:后端驱动的页面导航解决方案
    在Ja开发中,实现“下一页”功能是分页技术中的一个重要环节,尤其是在处理大量数据时。对于后端驱动的页面导航解决方案,核心思想是通过服务器来处理分页逻辑,确保前端能够高效获取到数据。这不仅可以减轻前端的负担,还能提高整体系统的性能和用户体验。分页功能的基础概念分页是将......