生产者与消费者

article/2025/9/12 16:56:21

生产者和消费者

目录

      • 生产者和消费者
        • 1.什么是生产者和消费者
        • 2.生产者和消费者(不加唤醒机制)
        • 3.生产者和消费者(加唤醒机制)
        • 4.解决虚假唤醒
        • 5.使用lock锁
        • 6.面试题

1.什么是生产者和消费者

​ 在日常生活中,我们去商店买东西,我们就是消费者,商店里面的某件商品有可能被卖完,那么这件商品就要生产(即进货),要保持两者统一。不能说顾客去买东西,商店里面没有就没有了,当商店里面没有东西之后就要去生产(进货),商店也是有限容量的,一件商品不能放太多,有个固定容量,生产的时候不能超过这个容量。

2.生产者和消费者(不加唤醒机制)

public class ProductorAndConsumer {public static void main(String[] args) {Market market = new Market();Productor productor = new Productor(market);Consumer consumer = new Consumer(market);new Thread(productor,"生产者A").start();new Thread(consumer,"消费者B").start();}
}//商店
class Market{//某件商品数量,最开始为0private int product=0;//进货方法,在多线程环境下,如果不加锁会产生线程安全问题,这里加synchronized锁public synchronized void get(){//限定商店容量为10if(product>=10){System.out.println("仓库已满!");}else{System.out.println(Thread.currentThread().getName()+"进货成功!-->"+ ++product);}}//出售方法public synchronized void sale(){if(product<=0){System.out.println("已售罄!");}else {System.out.println(Thread.currentThread().getName()+"出售成功-->"+ --product);}}
}//生产者,生产者不可能只有一个,所以是多线程的
class Productor implements Runnable{private Market market;public Productor(Market market){this.market=market;}@Overridepublic void run() {//一次买15个for (int i=0;i<15;i++){market.get();}}
}//消费者
class Consumer implements Runnable{private Market market;public Consumer(){}public Consumer(Market market){this.market=market;}@Overridepublic void run() {//一次买10个for (int i=0;i<10;i++){market.sale();}}
}

​ 当不加唤醒机制的生产者和消费者模式,出现售罄情况时不会立即去生产,出现仓库已满情况时也不会立即去出售,如下:
在这里插入图片描述

3.生产者和消费者(加唤醒机制)

public class ProductorAndConsumer {public static void main(String[] args) {Market market = new Market();Productor productor = new Productor(market);Consumer consumer = new Consumer(market);new Thread(productor,"生产者A").start();new Thread(consumer,"消费者B").start();}
}//商店
class Market{//某件商品数量,最开始为0private int product=0;//进货方法,在多线程环境下,如果不加锁会产生线程安全问题,这里加synchronized锁public synchronized void get(){//限定商店容量为10if(product>=10){System.out.println("仓库已满!");//当仓库已满,需要停止生产try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}else{System.out.println(Thread.currentThread().getName()+"进货成功!-->"+ ++product);//当进货成功,就需要唤醒this.notifyAll();}}//出售方法public synchronized void sale(){if(product<=0){System.out.println("已售罄!");//售罄之后需要停止去生产try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}else {System.out.println(Thread.currentThread().getName()+"出售成功-->"+ --product);//出售成功之后需要生产this.notifyAll();}}
}//生产者,生产者不可能只有一个,所以是多线程的
class Productor implements Runnable{private Market market;public Productor(Market market){this.market=market;}@Overridepublic void run() {//一次买15个for (int i=0;i<15;i++){market.get();}}
}//消费者
class Consumer implements Runnable{private Market market;public Consumer(){}public Consumer(Market market){this.market=market;}@Overridepublic void run() {//一次买10个for (int i=0;i<10;i++){market.sale();}}
}

​ 以上代码看起来没有什么问题,但是在实际运行时程序没有停止,如下图:
在这里插入图片描述

​ 分析问题原因,当生产者循环到最后一次时,消费者还有两次循环,当商品售罄之后,消费者会wait(),而生产者的循环已经结束,那么程序就一直会卡在wait(),不会结束。

4.解决虚假唤醒

​ 为了解决3出的问题,那么就要让消费者的最后一次循环中可以执行notifyAll()方法,这样才可以结束。

​ 那么就可以把else去掉,这样就可以使消费者最后一次执行结束,代码如下:

public class ProductorAndConsumer {public static void main(String[] args) {Market market = new Market();Productor productor = new Productor(market);Consumer consumer = new Consumer(market);new Thread(productor,"生产者A").start();new Thread(consumer,"消费者B").start();new Thread(productor,"生产者C").start();new Thread(consumer,"消费者D").start();}
}//商店
class Market{//某件商品数量,最开始为0private int product=0;//进货方法,在多线程环境下,如果不加锁会产生线程安全问题,这里加synchronized锁public synchronized void get(){//限定商店容量为10while(product>10){System.out.println("仓库已满!");//当仓库已满,需要停止生产try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+"进货成功!-->"+ ++product);//当进货成功,就需要唤醒this.notifyAll();}//出售方法public synchronized void sale(){while(product<=0){System.out.println("已售罄!");//售罄之后需要停止去生产try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+"出售成功-->"+ --product);//出售成功之后需要生产this.notifyAll();}
}//生产者,生产者不可能只有一个,所以是多线程的
class Productor implements Runnable{private Market market;public Productor(Market market){this.market=market;}@Overridepublic void run() {//一次买15个for (int i=0;i<15;i++){market.get();}}
}//消费者
class Consumer implements Runnable{private Market market;public Consumer(){}public Consumer(Market market){this.market=market;}@Overridepublic void run() {//一次买10个for (int i=0;i<10;i++){market.sale();}}
}

​ 在修改以上代码中还出现了”虚假唤醒“的问题,即存在多个生产者和消费者的时候,当商店没有商品时,两个消费者都停在了wait()方法,然后生产者进行生产,生产一件之后,会执行notifyAll()方法,那么两个消费者都会去执行,就会出现商品不够的现象。

​ 为了解决以上问题,将if判断改成了while判断,生产者notifyAll()之后,消费者还需再次判断商品是否足够,不够继续wait()。

5.使用lock锁

​ 之前我们使用的是synchronized锁,我们现在改为lock同步锁进行替换。

【注意】:

  • 使用lock锁的时候,必须要对其进行释放,一般放在finally中
  • 使用lock锁之后,需要使用Condition进行唤醒操作
  • wait()方法变为await()方法
  • notify()变为singnal()方法
  • notifyAll()变为signalAll()方法
public class ProductorAndConsumer {public static void main(String[] args) {Market market = new Market();Productor productor = new Productor(market);Consumer consumer = new Consumer(market);new Thread(productor,"生产者A").start();new Thread(consumer,"消费者B").start();new Thread(productor,"生产者C").start();new Thread(consumer,"消费者D").start();}
}//商店
class Market{//某件商品数量,最开始为0private int product=0;private Lock lock=new ReentrantLock();private Condition condition=lock.newCondition();//进货方法,在多线程环境下,如果不加锁会产生线程安全问题,这里加synchronized锁public  void get(){lock.lock();try{//限定商店容量为10while(product>10){System.out.println("仓库已满!");//当仓库已满,需要停止生产try {condition.await();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+"进货成功!-->"+ ++product);//当进货成功,就需要唤醒condition.signalAll();}finally {lock.unlock();}}//出售方法public synchronized void sale(){lock.lock();try{while(product<=0){System.out.println("已售罄!");//售罄之后需要停止去生产try {condition.await();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+"出售成功-->"+ --product);//出售成功之后需要生产condition.signalAll();}finally {lock.unlock();}}
}//生产者,生产者不可能只有一个,所以是多线程的
class Productor implements Runnable{private Market market;public Productor(Market market){this.market=market;}@Overridepublic void run() {//一次买15个for (int i=0;i<15;i++){market.get();}}
}//消费者
class Consumer implements Runnable{private Market market;public Consumer(){}public Consumer(Market market){this.market=market;}@Overridepublic void run() {//一次买10个for (int i=0;i<10;i++){market.sale();}}
}

6.面试题

​ 使用多线程实现先打印线程AA打印”AA“5次,线程BB打印”BB“10次,线程CC打印”CC“15次,总共打印10轮。

​ 【使用Lock中的Condition】

public class test {private static Integer a = 1;public static void main(String[] args) {/*** 线程的创建方式:*  1.new Thread*  2.Callable*  3.Runnable*  4.线程池*/add add = new add();new Thread(()->{for (int i=0;i<10;i++){add.printAA(i);}},"AA").start();new Thread(()->{for (int i=0;i<10;i++){add.printBB(i);}},"BB").start();new Thread(()->{for (int i=0;i<10;i++){add.printCC(i);}},"CC").start();}
}class add{private int flag=1;private Lock lock=new ReentrantLock();Condition condition1=lock.newCondition();Condition condition2=lock.newCondition();Condition condition3=lock.newCondition();//打印AAvoid printAA(int loop){lock.lock();try{while (flag!=1){try {condition1.await();} catch (InterruptedException e) {e.printStackTrace();}}for (int i = 0; i < 5; i++) {System.out.println("AA"+"轮数:"+(loop+1));}flag++;condition2.signal();}finally {lock.unlock();}}//打印BBvoid printBB(int loop){lock.lock();try{while (flag!=2){try {condition2.await();} catch (InterruptedException e) {e.printStackTrace();}}for (int i = 0; i < 10; i++) {System.out.println("BB"+"轮数:"+(loop+1));}flag++;condition3.signal();}finally {lock.unlock();}}//打印CCvoid printCC(int loop){lock.lock();try{while (flag!=3){try {condition3.await();} catch (InterruptedException e) {e.printStackTrace();}}for (int i = 0; i < 15; i++) {System.out.println("CC"+"轮数:"+(loop+1));}flag=1;condition1.signal();}finally {lock.unlock();}}}

【使用Object中的wait、notify、notifyAll】

public class test {private static Integer a = 1;public static void main(String[] args) {/*** 线程的创建方式:*  1.new Thread*  2.Callable*  3.Runnable*  4.线程池*/add add = new add();new Thread(()->{for (int i=0;i<10;i++){add.printAA(i);}},"AA").start();new Thread(()->{for (int i=0;i<10;i++){add.printBB(i);}},"BB").start();new Thread(()->{for (int i=0;i<10;i++){add.printCC(i);}},"CC").start();}
}class add{private int flag=1;//打印AAsynchronized void printAA(int loop){while (flag!=1){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}flag++;for (int i = 0; i < 5; i++) {System.out.println("AA"+"轮数:"+(loop+1));}this.notifyAll();}//打印BBsynchronized void printBB(int loop){while (flag!=2){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}flag++;System.out.println("BB"+"轮数:"+(loop+1));this.notifyAll();}//打印CCsynchronized void printCC(int loop){while (flag!=3){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}flag=1;System.out.println("CC"+"轮数:"+(loop+1));this.notifyAll();}}

http://chatgpt.dhexx.cn/article/HpKXSosh.shtml

相关文章

三种方式实现生产者-消费者模型

前言 生产者消费者问题&#xff08;英语&#xff1a;Producer-consumer problem&#xff09;&#xff0c;也称有限缓冲问题&#xff08;英语&#xff1a;Bounded-buffer problem&#xff09;&#xff0c;是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的…

生产者消费者模型

目录 一、生产者消费者模型的概念 二、生产者消费者模型的特点 三、生产者消费者模型优点 四、基于BlockingQueue的生产者消费者模型 4.1 基本认识 4.2 模拟实现 五、POSIX信号量 5.1 信号量概念 5.2 信号量函数 5.2.1 初始化信号量 5.2.2 销毁信号量 5.2.3 等待信…

打家劫舍问题

打家劫舍问题 最近碰见这种问题实在是太多了,感觉还是有必要学习一下打家劫舍以及其变种问题这一类问题采用的都是动态规划的解法 一些练习题目 6378. 最小化旅行的价格总和 198. 打家劫舍I 213. 打家劫舍 II 337. 打家劫舍 III 2560. 打家劫舍 IV 1 、打家劫舍I 题目…

经典动态规划:打家劫舍系列问题

打家劫舍系列总共有三道&#xff0c;难度设计非常合理&#xff0c;层层递进。第一道是比较标准的动态规划问题&#xff0c;而第二道融入了环形数组的条件&#xff0c;第三道更绝&#xff0c;让盗贼在二叉树上打劫. House Robber | public int rob(int[] nums);题目很容易理解…

【算法】动态规划(三)——打家劫舍系列问题

目录 一、前言 二、打家劫舍 &#xff08;1&#xff09;198. 打家劫舍Ⅰ • 整体代码&#xff1a; &#xff08;2&#xff09;213. 打家劫舍 II • 题目分析 • 整体代码&#xff1a; &#xff08;3&#xff09;337. 打家劫舍Ⅲ • 思路分析 • 整体代码&#xff1a; 三、补充知…

动态规划之打家劫舍系列

前言 打家劫舍问题是一种非常经典的有限制条件的动态规划问题&#xff0c;按理说&#xff0c;不是一种特殊的类型&#xff0c;但是因为力扣上纯纯的出了三道题&#xff08;1&#xff0c;2&#xff0c;3&#xff09;来考察&#xff0c;题目的难度是依次递进的&#xff0c;还结合…

动态规划之打家劫舍

动态规划之打家劫舍 文章目录 动态规划之打家劫舍1. "198. 打家劫舍"2. "198. 打家劫舍&#xff08;变种&#xff1a;输出路径&#xff09;"3. "213. 打家劫舍 II"4. "337. 打家劫舍 III" 1. “198. 打家劫舍” dp数组定义&#xff1a…

oracle 根据部分字段去重

问题&#xff1a;在oracle中使用group by分组&#xff0c;group by子句中必须包含所有的select中的字段和order by子句中的字段。 在不使用group by子句的情况下&#xff0c;进行分组。&#xff08;根据部分字段分组&#xff09; over()分析函数 原sql SELECTIM. ID mediaGrou…

oracle字段去重查询,oracle怎么去重查询

oracle去重查询的方法是&#xff1a; oracle 数据库多字段去重 方法介绍&#xff1a;distinct 关键字、group by 、row_number ()over(partition by 列 order by 列 desc) 我的需求是&#xff1a;根据某几列去重 查询出去重后的全部信息。最后我选择的是第三种方法。 我的想法&…

oracle 数据去重方法

1. 创建表&#xff1a; -- Create table create table TEST_USER (user_id NUMBER(3),user_name VARCHAR2(20),user_age NUMBER(3) ) tablespace GUAN_TABLESPACEpctfree 10initrans 1maxtrans 255storage(initial 64Knext 1Mminextents 1maxextents unlimited);--测试数据…

oracle 字符串去重

select regexp_replace(1,1,3,5,5, ([^,])(,\1)*(,|$), \1\3) from dual;注意&#xff1a; 但是&#xff0c;这个去重&#xff0c;必须建立在排序的基础上&#xff0c;如果listagg拼接出来的数值像 a, b, a, c 这时候&#xff0c;该正则就会失效。

MYSQL/ORACLE多字段去重-根据某字段去重

通过百度上的答案多数无效 自己搞了个 使用oracle row_number()函数&#xff0c;给每个同名的加一个序号&#xff0c;最后筛选第n个想同的即可 oracle与mysql不同 1.oracel 多字段distinct(字段名去重) group by去重失效 可以用row_number() over(partition) 给同名列加个序号…

Oracle 数据去重

在Oracle数据库中删除重复数据 一&#xff0c;查询及删除重复记录的SQL语句 Person01表&#xff1a; 1. 查询表中多余的重复数据&#xff0c;根据ID字段来判断是否重复 SELECT * FROM PERSON01 WHERE ID IN (SELECT ID FROM PERSON01 GROUP BY ID HAVING COUNT(ID) > 1)…

Oracle根据多列去重

&#xff08;1&#xff09;distinct 关键词 distinct用于返回唯一不同的值&#xff0c;可作用于单列和多列 但必须将其放在开头&#xff0c;否则会提示错误 而若在其后添加多个变量名&#xff0c;则返回的将是这多个变量名不同时重复的列&#xff0c;因而使用distinct筛选某…

oracle 数据库去重查询

oracle数据库中有如下一张表&#xff0c;包含id,loginid,name,researchtime等字段&#xff0c;其中name字段中的数据有重复&#xff0c;查询数据时要重复数据只取一条&#xff0c;利用row_number ()over(partition by 列 order by 列 desc)方法实现 1:select a.,row_number() o…

oracle去重函数

1、distinct &#xff08;1&#xff09;、常用的distinct select distinct column from table; &#xff08;2&#xff09;、统计去重后数量 select count(distinct column) from table;–查去重后数量 &#xff08;3&#xff09;、distinct必须放在开头 select id, distinct n…

oracle 数据库 去重查询

oracle 数据库多字段去重 方法介绍&#xff1a;distinct 关键字、group by 、row_number ()over(partition by 列 order by 列 desc) 我的需求是&#xff1a;根据某几列去重 查询出去重后的全部信息。最后我选择的是第三种方法。 我的想法&#xff1a;我想找出一种更简单的方…

Oracle实现去重的两种方式总结

业务场景 需要查询某数据&#xff0c;由于需要三张表关联查询&#xff0c;查询结果如下&#xff1a; 原始SQL语句 SELECT D.ORDER_NUM AS "申请单号" ,D.CREATE_TIME ,D.EMP_NAME AS "申请人",(SELECT extractvalue(t1.row_data,/root/row/FI13_wasteNam…

mysql默认密码的查找与修改

注&#xff1a;此方法仅可用于初始安装数据库或学习时使用&#xff0c;在实际生产中会使所有数据库文件删除&#xff0c;故应先提前备份相关重要数据&#xff0c;以免造成不必要的损失&#xff0c;请谨慎使用。 若使用mysqld –initialize初始化mysql数据库&#xff0c;会产生一…

rpm安装mysql后密码_CentOs安装Mysql和配置初始密码

装载自&#xff1a;https://www.cnblogs.com/FlyingPuPu/p/7783735.html 一、Mysql下载安装 使用上传命令上传至/home目录&#xff0c;如&#xff1a;rz命令(yum install -y lrzsz) 添加mysql仓库(-Uvh后面接的为你下载的rpm文件名) sudo rpm -Uvh mysql57-community-release-e…