当前位置: 首页 > ai >正文

Java_多线程_生产者消费者模型_互斥锁,阻塞队列

生产者消费者模型(Producer-Consumer Model)是计算机科学中一个经典的并发编程模型,用于解决多线程/多进程环境下的协作问题。

基本概念
生产者:负责生成数据或任务的实体
消费者:负责处理数据或执行任务的实体
缓冲区:生产者与消费者之间共享的数据存储区域
模型特点
生产者与消费者以不同的速度运行
两者通过共享的缓冲区进行通信
缓冲区有大小限制,可能满或空
需要解决的问题
同步问题:
当缓冲区满时,生产者需要等待
当缓冲区空时,消费者需要等待
互斥问题:
对缓冲区的访问必须是互斥的,防止数据竞争
常见实现方式
使用信号量(Semaphore):空缓冲区信号量满缓冲区信号量互斥信号量
使用条件变量(Condition Variable)和互斥锁(Mutex)
使用阻塞队列(高级语言中常用)

本篇我们使用互斥锁和阻塞队列来解决这个问题
在多线程的锁中我们首先要避免的就是死锁这个问题,在 Java 中,Lock 接口及其实现类(如 ReentrantLock)是在 JDK 5(Java 5) 引入的,属于java.util.concurrent.locks 包的一部分。我们这里使用Java的synchronized实现
首先来分析问题,我们可以抽象的将生产者消费者问题想象为,厨师和顾客的问题:
顾客:

  1. 判断桌子上是否有食物
  2. 如果没有就等待
  3. 如果有就直接吃掉
  4. 吃完食物之后,通知厨师继续做食物

厨师:

  1. 判断桌子上是否有食物
  2. 如果桌子上有食物的话就等待
  3. 如果桌子上没有食物的话就制作食物
  4. 将食物放置在桌子上
  5. 唤醒等待的顾客开始吃

分析完毕,首先我们应该先新建三个类分别为:Cook(厨师类),Customer(顾客类),Desk(桌子类)
初始化桌子:

  1. 初始化桌子上食物的标志,0为无食物,1为有食物
  2. 初始化顾客的上限,例如顾客最多吃10份食物
package Thread.Producer_Consumer;public class Desk {public static int FoodFlag=0;//食物当前的状态表示当前桌子上是否有食物public static int count=10;//消费者最多可以吃10个食物public static Object lock=new Object();////创建一个锁对象,用于生产者和消费者线程间的同步
}//由于是所有线程共同的变量所以我们使用static关键字修饰

接下来开始完成顾客线程

package Thread.Producer_Consumer;public class Customer extends  Thread{//消费者线程@Overridepublic void run() {while( true){synchronized(Desk.lock){// 检查是否达到食物上限(count=0表示不能再吃)if(Desk.count==0){break;}else {// 检查桌子上是否有食物(FoodFlag=1表示有食物)if (Desk.FoodFlag == 1) {System.out.println("顾客吃掉食物");Desk.FoodFlag = 0;//表示没有食物Desk.count--;//剩余可吃食物数量减1System.out.println("顾客还可以吃"+Desk.count);Desk.lock.notifyAll();//唤醒lock锁中所有等待的线程} else {//桌上没有食物,则等待try {Desk.lock.wait();//释放锁,并进入等待状态} catch (InterruptedException e) {throw new RuntimeException(e);}}}}}System.out.println("顾客吃饱了,结束消费!");}
}

厨师线程

package Thread.Producer_Consumer;public class Cook extends Thread{@Overridepublic void run() {while (true) {synchronized (Desk.lock){//如果消费者可以吃的食物的数量已经达到最大,那么则直接退出if (Desk.count==0) {break;}else{//如果桌子有食物,等待消费者进程if(Desk.FoodFlag==1){try {Desk.lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}else{//如果桌子没有食物System.out.println("生产者正在生产食物...");//设置桌子有食物Desk.FoodFlag=1;//唤醒消费者线程Desk.lock.notifyAll();}}}}}
}

最后创建一个测试类

package Thread.Producer_Consumer;public class Test {public static void main(String[] args) {Desk desk=new Desk();Customer f1=new Customer();Cook c1=new Cook();f1.setName("消费者1");c1.setName("生产者1");f1.start();c1.start();}
}

在这里插入图片描述
下来我们使用阻塞队列来实现一下:
桌子类:

package Thread.Producer_Consumer_2;public class Desk {public static int count=10;//消费者最多可以吃10个食物public static int Food_max=10;
}

厨师类:

package Thread.Producer_Consumer_2;import java.util.concurrent.ArrayBlockingQueue;public class Cook extends  Thread{ArrayBlockingQueue<String> queue;public Cook(ArrayBlockingQueue<String> queue){this.queue=queue;}//构造方法,创建一个阻塞队列@Overridepublic void run() {//厨师不断将食物放进队列中while(true){if(Desk.Food_max<=0){break;}else{try {queue.put("食物");System.out.println("厨师放了一个食物");Desk.Food_max--;} catch (InterruptedException e) {throw new RuntimeException(e);}}}}
}

顾客类:

package Thread.Producer_Consumer_2;import Thread.Producer_Consumer.Desk;import java.util.concurrent.ArrayBlockingQueue;public class Customer extends  Thread{//消费者线程ArrayBlockingQueue<String> queue;public Customer(ArrayBlockingQueue<String> queue){this.queue=queue;}@Overridepublic void run() {while( true){if(Desk.count==0){System.out.println("顾客吃到上限了");break;}else{try {queue.take();System.out.println("消费者吃掉了一个食物");Desk.count--;} catch (InterruptedException e) {throw new RuntimeException(e);}}}}
}

测试类:

package Thread.Producer_Consumer_2;import Thread.Producer_Consumer.Desk;import java.util.concurrent.ArrayBlockingQueue;public class Test {public static void main(String[] args) {ArrayBlockingQueue<String> list=new ArrayBlockingQueue<>(5);//创建一个阻塞队列Customer f1=new Customer(list);Cook c1=new Cook(list);f1.setName("消费者1");c1.setName("生产者1");f1.start();c1.start();}
}

阻塞队列总结:
在创建阻塞队列的时候,需要创建实现类的对象,我们可以通过查看源码的形式来看一下
在这里插入图片描述
ArrayBlockingQueue实现了BlockingQueue这个接口
在这里插入图片描述
BlockingQueue又继承于Queue
在这里插入图片描述
Queue又继承于Collection
在这里插入图片描述
Collection又继承于Iterable,由此我们也就可以得出,阻塞队列是可以通过for_each循环遍历的
在这里插入图片描述
回归主题,在ArrayBlockingQueue中,有一个带参的构造方法,由此来创建阻塞队列,capacity代表阻塞队列的容量(不要忘记了泛型代表了阻塞队列的参数为哪种类型)
在这里插入图片描述
同时,LinkBlockingQueue也实现了BlockingQueue这个接口
在这里插入图片描述
由此可以得出一个图:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
由于put()和take()方法都是自带锁的,所以我们并不用手动设置锁,同时,由于我们代码中的输出语句在锁的外面在这里插入图片描述
这导致了输出时偶尔并不能按照我们的想法进行输出,但是执行时一定是正确的

http://www.xdnf.cn/news/16068.html

相关文章:

  • Java与NLP实战:文本处理到情感分析全解析
  • Ethereum: 从 1e+21 到千枚以太币:解密 Geth 控制台的余额查询
  • 适配器模式——以springboot为例
  • 《云计算蓝皮书 2025 》发布:云计算加速成为智能时代核心引擎
  • MySQL--day13--视图存储过程与函数
  • 垃圾回收GC
  • 【AI News | 20250722】每日AI进展
  • Java应用程序内存占用分析
  • 什么是HTTP长连接、短连接?谁更能抗DoS攻击?
  • 【数据库】国产数据库的新机遇:电科金仓以融合技术同步全球竞争
  • Python进阶知识之pandas库
  • 图论的题目整合(Dijkstra)
  • 欧盟网络安全标准草案EN 18031详解
  • ESP32-S3学习笔记<5>:SPI的应用
  • Redis 的事务机制是怎样的?
  • freqtrade在docker运行一个dryrun实例
  • UI自动化测试实战
  • mysql什么时候用char,varchar,text,longtext
  • odoo欧度小程序——添加用户
  • Fluent许可与硬件绑定的解决方法
  • Spring Data Redis 从入门到精通:原理与实战指南
  • C++刷题 - 7.23
  • kettle 8.2 ETL项目【一、初始化数据库及介绍】
  • 【MySQL】MySQL 索引详解
  • UniappDay01
  • 计算机毕设分享-基于SpringBoot的房屋租赁系统(开题报告+源码+Lun文+开发文档+数据库设计文档)
  • 【Spring Cloud Gateway 实战系列】进阶篇:过滤器高级用法、动态路由配置与性能优化
  • 【计算机网络】正/反向代理服务器,有状态/无状态应用
  • 漏洞生命周期管理:从发现到防护的全流程方案
  • AI产品经理面试宝典第48天:产品设计与用户体验优化策略