JAVA多线程之生产者消费者模型

article/2025/9/12 16:03:03

生产者消费者模型

所谓的生产者消费者模型,是通过一个容器来解决生产者和消费者的强耦合问题。通俗的讲,就是生产者在不断的生产,消费者也在不断的消费,可是消费者消费的产品是生产者生产的,这就必然存在一个中间容器,我们可以把这个容器想象成是一个货架,当货架空的时候,生产者要生产产品,此时消费者在等待生产者往货架上生产产品,而当货架满的时候,消费者可以从货架上拿走商品,生产者此时等待货架的空位,这样不断的循环。那么在这个过程中,生产者和消费者是不直接接触的,所谓的‘货架’其实就是一个阻塞队列,生产者生产的产品不直接给消费者消费,而是仍给阻塞队列,这个阻塞队列就是来解决生产者消费者的强耦合的。就是生产者消费者模型。

总结一下:生产者消费者能够解决的问题如下:

  • 生产与消费的速度不匹配
  • 软件开发过程中解耦

在具体实现生产者消费者模型之前需要先描述几个用到的方法:

wait()

先看一下wait()是干什么的?

1.wait()是Object里面的方法,而不是Thread里面的,这一点很容易搞错。它的作用是将当前线程置于预执行队列,并在wait()所在的代码处停止,等待唤醒通知。
2.wait()只能在同步代码块或者同步方法中执行,如果调用wait()方法,而没有持有适当的锁,就会抛出异常。
wait()方法调用后悔释放出锁,线程与其他线程竞争重新获取锁。

举个例子:

public class TestWait implements Runnable {private final Object object=new Object();@Overridepublic void run() {synchronized (object){System.out.println("线程执行开始。。。");try {object.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程执行结束。。。");}}public static void main(String[] args) {TestWait testWait=new TestWait();Thread thread=new Thread(testWait);thread.start();}
}

结果如下:
在这里插入图片描述

从结果中我们可以看出线程调用了wait()方法后一直在等待,不会继续往下执行。这也就能解释上面说的wait()一旦执行,除非接收到唤醒操作或者是异常中断,否则不会继续往下执行。

notify()方法

在上面的代码中我们看到wait()调用以后线程一直在等待,在实际当中我们难免不希望是这样的,那么这个时候就用到了另一个方法notify方法:

1.notify()方法也是要在同步代码块或者同步方法中调用的,它的作用是使停止的线程继续执行,调用notify()方法后,会通知那些等待当前线程对象锁的线程,并使它们重新获取该线程的对象锁,如果等待线程比较多的时候,则有线程规划器随机挑选出一个呈wait状态的线程。
2.notify()调用之后不会立即释放锁,而是当执行notify()的线程执行完成,即退出同步代码块或同步方法时,才会释放对象锁。

还是上面的例子,刚才我们调用了wait()方法后,线程便一直在等待,接下来我们给线程一个唤醒的信号,代码如下:

public class TestWait implements Runnable {private final Object object=new Object();public void setFlag(boolean flag) {this.flag = flag;}private boolean flag=true;@Overridepublic void run() {if(flag){this.testwait();}else {this.testnotify();}}public void testwait(){synchronized (object){try {System.out.println("线程开始执行。。。");Thread.sleep(1000);object.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程执行结束。。。");}}public void testnotify(){synchronized (object){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}object.notify();}}public static void main(String[] args) {TestWait testWait=new TestWait();Thread thread=new Thread(testWait);thread.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}testWait.setFlag(false);Thread thread1=new Thread(testWait);thread1.start();}
}

结果如下:
在这里插入图片描述
我们看到在调用notify()方法之后,线程又继续了。

notifyAll()方法

从字面意思就可以看出notifyAll是唤醒所有等待的线程。

public class TestWait implements Runnable {private final Object object=new Object();private boolean flag=true;public void setFlag(boolean flag) {this.flag = flag;}@Overridepublic void run() {if(flag){this.testwait();}else {this.testnotify();}}public void testwait(){synchronized (object){try {System.out.println(Thread.currentThread().getName()+"线程开始执行。。。");Thread.sleep(1000);object.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"线程执行结束。。。");}}public void testnotify(){synchronized (object){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}object.notifyAll();}}public static void main(String[] args) {TestWait testWait=new TestWait();Thread thread=new Thread(testWait,"线程1");thread.start();Thread thread1=new Thread(testWait,"线程2");thread1.start();try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}testWait.setFlag(false);Thread thread2=new Thread(testWait);thread2.start();}
}

结果如下:
在这里插入图片描述
可见notifyAll()方法确实唤醒了所有等待的线程。

小结

出现阻塞的情况大体分为如下5种:

  1. 线程调用 sleep方法,主动放弃占用的处理器资源。
  2. 线程调用了阻塞式IO方法,在该方法返回前,该线程被阻塞。
  3. 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。
  4. 线程等待某个通知。
  5. 程序调用了 suspend方法将该线程挂起。此方法容易导致死锁,尽量避免使用该方法。

run()方法运行结束后进入销毁阶段,整个线程执行完毕。

每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待CPU的调度;反之,一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒。

生产者消费者模型代码示例

商品类

public class Goods {private int id;private String name;public Goods(int id, String name) {this.id = id;this.name = name;}
}

生产者类

public class Producer implements Runnable {private Goods goods;@Overridepublic void run() {while (true) {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (TestPC.queue) {goods=new Goods(1,"商品");if (TestPC.queue.size()<MAX_POOL) {TestPC.queue.add(goods);System.out.println(Thread.currentThread().getName()+"生产商品");} else {try {TestPC.queue.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}
}

消费者类

public class Consumer implements Runnable {@Overridepublic void run() {while (true){try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (TestPC.queue){if(!TestPC.queue.isEmpty()){TestPC.queue.poll();System.out.println(Thread.currentThread().getName()+"消费商品");}else {TestPC.queue.notify();}}}}
}

测试类

public class TestPC {public static final int MAX_POOL=10;public static final int MAX_PRODUCER=5;public static final int MAX_CONSUMER=4;public static  Queue<Goods> queue=new ArrayBlockingQueue<>(MAX_POOL);public static void main(String[] args) {Producer producer=new Producer();Consumer consumer=new Consumer();for(int i=0;i<MAX_PRODUCER;i++) {Thread threadA = new Thread(producer, "生产者线程"+i);threadA.start();}for(int j=0;j<MAX_CONSUMER;j++) {Thread threadB = new Thread(consumer, "消费者线程"+j);threadB.start();}}
}

部分结果展示:
在这里插入图片描述


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

相关文章

Linux生产者消费者模型

文章目录 生产者消费者模型生产者消费者模型的概念生产者消费者模型的特点生产者消费者模型优点 基于BlockingQueue的生产者消费者模型基于阻塞队列的生产者消费者模型模拟实现基于阻塞队列的生产消费模型 生产者消费者模型 生产者消费者模型的概念 生产者消费者模式就是通过一…

生产者消费者模型你知道多少

背景 进入正题之前先说点故事。从最开始学java的那里开始&#xff1a;我是从08年下半年开始学Java&#xff0c;在《我的六年程序之路》中提到了一些。当时比较简单&#xff0c;每天看尚学堂的视频&#xff08;对于初学者而言看视频好一些。&#xff09;&#xff0c;然后写代码。…

生产者消费者模型详解

生产者消费者模型 文章目录 生产者消费者模型什么是生产者消费者模型基于BlockingQueue的生产者消费者模型单生产者单消费者模型多生产者多消费者模型 什么是生产者消费者模型 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接…

Python -- 生产者消费者

代码 # -*- coding: utf-8 -*- # Author : markadc # Time : 2021/4/14 11:43from queue import Queue import time import threading# maxsize: 指定队列最大长度 q Queue(maxsize10)# 生产者 def product(name):count 0while True:# 只要队列没有满&#xff0c;就一直…

生产者与消费者

生产者和消费者 目录 生产者和消费者1.什么是生产者和消费者2.生产者和消费者(不加唤醒机制)3.生产者和消费者(加唤醒机制)4.解决虚假唤醒5.使用lock锁6.面试题 1.什么是生产者和消费者 ​ 在日常生活中&#xff0c;我们去商店买东西&#xff0c;我们就是消费者&#xff0c;商…

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

前言 生产者消费者问题&#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…