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

article/2025/9/29 10:25:38

目录

    • 1.什么是生产者消费者模式:
    • 2.生产者消费者模型的实现:
    • 第一种:使用 synchronized和wait、notify
    • 第二种:使用 Lock和await、signal
    • 第三种:使用 阻塞队列 BlockingQueue

1.什么是生产者消费者模式:

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力

这个阻塞队列就是用来给生产者和消费者解耦的。纵观大多数设计模式,都会找一个第三者出来进行解耦,如工厂模式的第三者是工厂类,模板模式的第三者是模板类。在学习一些设计模式的过程中,如果先找到这个模式的第三者,能帮助我们快速熟悉一个设计模式
在这里插入图片描述

在这里插入图片描述

如上图所示:当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列。

在这里插入图片描述

如上图所示:当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒。

2.生产者消费者模型的实现:

生产者是一堆线程,消费者是另一堆线程,内存缓冲区可以使用List数组队列,数据类型只需要定义一个简单的类就好。关键是如何处理多线程之间的协作。这其实也是多线程通信的一个范例。

在这个模型中,最关键就是内存缓冲区为空的时候消费者必须等待,而内存缓冲区满的时候,生产者必须等待。其他时候可以是个动态平衡。值得注意的是多线程对临界区资源(即共享资源)的操作时候必须保证在读写中只能存在一个线程,所以需要设计锁的策略。




第一种:使用 synchronized和wait、notify

package com.fan.blockqueue;
import java.util.concurrent.TimeUnit;
//使用 synchronized和wait、notify实现生产者和消费者
//1.定义资源类
class MyCacheResources1{int num = 0;//共享资源:生产和消费数字//资源池中实际存储的数据个数private int count = 0;//资源池中允许存放的资源数目private int capacity = 5;Object obj= new Object();//作为锁//生产方法public void product() throws InterruptedException {//使用代码块,精确加锁,且synchronized会自动释放锁synchronized (obj){//1.判断什么时候等待if(count == capacity){//当实际元素数量达到总容量是,生产阻塞等待obj.wait();}//2.干活num++;count++;//生产一个数字,元素数量+1System.out.println(Thread.currentThread().getName()+"生产了一个数字"+num+",资源池剩余数据个数:"+count);//3.干完后后通知唤醒  消费者来消费obj.notifyAll();//唤醒其他所有线程,让他们竞争锁}}//消费的方法public void consumer() throws InterruptedException {synchronized (obj){//1.判断什么时候等待if(count == 0){//当实际元素数量达到总容量是,生产阻塞等待obj.wait();}//2.干活num--;count--;//消费一个元素,数量-1System.out.println(Thread.currentThread().getName()+"消费了一个数字"+num+",资源池剩余数据个数:"+count);//3.干完后后通知唤醒  生产者obj.notifyAll();//唤醒其他所有线程,让他们竞争锁}}
}
public class ProductAndConsumerTest1 {public static void main(String[] args) {MyCacheResources1 myCacheResources1 = new MyCacheResources1();//这里我们直接使用匿名内部类的变相lamda表达式来创建线程//生产者new Thread(()->{for (int i = 1; i <=10 ; i++) {//生产10轮try {myCacheResources1.product();} catch (InterruptedException e) {e.printStackTrace();}}},"生产者").start();//消费者new Thread(()->{//让生产者先 生产数据for (int i = 1; i <=10 ; i++) {//消费10轮,try { TimeUnit.SECONDS.sleep(1);}catch (InterruptedException e) {e.printStackTrace();}try {myCacheResources1.consumer();} catch (InterruptedException e) {e.printStackTrace();}}},"消费者1").start();new Thread(()->{//让生产者先 生产数据for (int i = 1; i <=10 ; i++) {//消费10轮,try { TimeUnit.SECONDS.sleep(1);}catch (InterruptedException e) {e.printStackTrace();}try {myCacheResources1.consumer();} catch (InterruptedException e) {e.printStackTrace();}}},"消费者2").start();}
}

打印结果:
在这里插入图片描述

第二种:使用 Lock和await、signal

package com.fan.blockqueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;//定义资源类
class MyCacheResources2{int  num = 0;//共享资源:生产和消费数字//资源池中实际存储的数据个数private int count = 0;//资源池中允许存放的资源数目private int capacity = 5;//创建可重入的非公平锁Lock lock = new ReentrantLock();//使用两个条件队列condition来实现精确通知Condition productCondition = lock.newCondition();Condition consumerCondition = lock.newCondition();//定义操作资源类的方法:生产方法public void product() throws InterruptedException {lock.lock();//加锁try {//1.判断什么时候等待,并防止虚假唤醒while(count == capacity){//当达到最大容量,则阻塞等待,生产者阻塞productCondition.await();}//2.干活num++;//共享资源+1count++;//元素实际个数+1System.out.println(Thread.currentThread().getName()+"\t 生产了一个数字:"+num+",现在资源池剩余数据个数:"+count);//3.通知。生产者生产完立马通知消费者来消费consumerCondition.signal();//消费者条件队列被唤醒} finally {lock.unlock();//解锁}}//定义操作资源类的方法:生产方法public void consumer() throws InterruptedException {lock.lock();//加锁,同一把锁try {//1.判断什么时候等待,并防止虚假唤醒while(count == 0){//没数据时,则阻塞等待,消费者阻塞consumerCondition.await();}//2.干活//共享资源-1count--;//元素实际个数-1System.out.println(Thread.currentThread().getName()+"\t 消费了一个数字:"+(num--)+",现在资源池剩余数据个数:"+count);//3.通知。消费者 消费完 立马通知生产者来生产productCondition.signal();//生产者条件队列被唤醒} finally {lock.unlock();//解锁}}
}
public class ProductAndConsumerTest2 {public static void main(String[] args) {MyCacheResources2 myCacheResources2 = new MyCacheResources2();//可以定义多个生产者和消费者,这里分别定义了一个new Thread(()->{for (int i = 0; i < 10; i++) {//10轮生产try {try { TimeUnit.SECONDS.sleep(1);}//让生产者 先  生产catch (InterruptedException e) {e.printStackTrace();}myCacheResources2.product();} catch (InterruptedException e) {e.printStackTrace();}}},"生产者").start();new Thread(()->{for (int i = 0; i < 10; i++) {//10轮消费try {myCacheResources2.consumer();} catch (InterruptedException e) {e.printStackTrace();}}},"消费者").start();}
}

注意:Condition 可以达到精确通知哪个线程要被唤醒的。很方便。而synchronized办不到精确通知的效果

在这里插入图片描述

第三种:使用 阻塞队列 BlockingQueue

package com.fan.blockqueue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;//资源类
class MyResource3{private BlockingQueue blockingQueue = new ArrayBlockingQueue(3);//线程操作资源类//向资源池中添加资源public  void add() throws InterruptedException {try {//put自带锁和通知唤醒方法try { TimeUnit.SECONDS.sleep(1);}//模拟生产耗时1秒catch (InterruptedException e) {e.printStackTrace();}//put方法是自带锁的阻塞唤醒方法,不需要我们写锁,通知和唤醒blockingQueue.put(1);System.out.println("生产者"+Thread.currentThread().getName()+"生产一件资源,当前资源池有"+blockingQueue.size()+"个资源");} catch (InterruptedException e) {e.printStackTrace();}}//向资源池中移除资源public  void remove(){try {try { TimeUnit.SECONDS.sleep(1);}//模拟消费耗时1秒catch (InterruptedException e) {e.printStackTrace();}Object take = blockingQueue.take();//自带锁和通知唤醒方法System.out.println("消费者" + Thread.currentThread().getName() +"消耗一件资源," + "当前资源池有" + blockingQueue.size()+ "个资源");} catch (InterruptedException e) {e.printStackTrace();}}
}
//使用阻塞队列BlockingQueue解决生产者消费者
public class BlockQueueDemo2 {public static void main(String[] args) {MyResource3 myResource3 = new MyResource3();//这里可以通过for循环的次数控制生产者和消费者的比例,来模拟缓存区的缓存剩余情况for (int i = 1; i <= 5 ; i++) {//请变换生产者和消费者数量进行测试//模拟两个生产者线程new Thread(()->{while(true){//循环生产try {myResource3.add();//生产数据} catch (InterruptedException e) {e.printStackTrace();}}},String.valueOf(i)).start();}for (int i = 1; i <= 2 ; i++) {//5个消费者new Thread(()->{while (true){//循环消费myResource3.remove();}},String.valueOf(i)).start();}}
}

在这里插入图片描述


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

相关文章

t-SNE算法

t-SNE(t-distributed stochastic neighbor embedding)是用于降维的一种机器学习算法&#xff0c;是由 Laurens van der Maaten 和 Geoffrey Hinton在 08 年提出来。t-SNE 是一种非线性降维算法&#xff0c;非常适用于高维数据降维到 2 维或者 3 维&#xff0c;进行可视化。在实…

t-SNE概述

为了循序渐进, 先来学习SNE. SNE 无论是多维数据还是词向量, 都是一个个散落在空间中的点, 点与点之间距离近的, 就可以看作属于同一分类或近义词. 衡量两点距离有很多种手段, 但最常用的还是欧式距离, 所以欧氏距离与相似度的关系可以用某种公式近似表达, 这样就可以把空间信…

机器学习笔记 - 什么是t-SNE?

1、t-SNE概述 t-Distributed Stochastic Neighbor Embedding (t-SNE) 是一种无监督的非线性技术,主要用于数据探索和高维数据的可视化。简单来说,t-SNE 让您对数据在高维空间中的排列方式有一种感觉或直觉。它由 Laurens van der Maatens 和 Geoffrey Hinton 于 2008 年提出。…

可视化降维方法 t-SNE

本篇主要介绍很好的降维方法t-SNE的原理 详细介绍了困惑度perplexity对有效点的影响首先介绍了SNE然后在SNE的基础上进行改进&#xff1a;1.使用对称式。2.低维空间概率计算使用t分布 t-SNE&#xff08;t分布和SNE的组合&#xff09; 以前的方法有个问题&#xff1a;只考虑相…

t-SNE非线性降维

TSNE&#xff08;t-Distributed Stochastic Neighbor Embedding &#xff09;是对SNE的改进&#xff0c;SNE最早出现在2002年&#xff0c;改变了MDN和ISOMAP中基于距离不变的思想&#xff0c;将高维映射到低维的同时&#xff0c;尽量保证相互之间的分布概率不变&#xff0c;SNE…

t-SNE原理及代码

SNE 基本原理 SNE是通过仿射变换将数据点映射到概率分布上&#xff0c;主要包括两个步骤&#xff1a;  &#xff11;) SNE构建一个高维对象之间的概率分布&#xff0c;使得相似的对象有更高的概率被选择&#xff0c;而不相似的对象有较低的概率被选择。   &#xff12;) SN…

t-SNE 原理及Python实例

由于毕业设计有部分工作需要对比两个图像数据集合的差异&#xff0c;为了可视化差异&#xff0c;利用了目前降维首选的t-SNE。我花了点时间看了sklearn里面关于这部分的文档&#xff0c;也查阅了相关博客&#xff0c;最终成功的将两种图片数据集作了一个可视化的对比。我觉得这…

t-SNE算法解析与简单代码实现

t-SNE算法解析与简单代码实现 t-SNESNE基本原理和介绍SNE原理推导t-SNE的引入Symmetric SNE拥挤现象关于 σ \sigma σ的求法 代码解析参数说明 Reference t-SNE t-SNE感觉就是将两个数据点的相似度转换为实际距离的算法 t-SNE(t-distributed stochastic neighbor embedding)是…

t-SNE

t-SNE 文章目录 t-SNE原理SNE(Stochastic Neighbor Embedding)t-SNE对称SNE拥挤问题不匹配的尾部可以补偿不匹配的维度 sklearn.manifold.TSNE参数返回对象的属性Methods 附录Kullback-Leibler divergencest-distributionmanifold learning&#xff08;流形学习&#xff09;Swi…

【33】t-SNE原理介绍与对手写数字MNIST的可视化结果

如有错误&#xff0c;恳请指出。 这篇博客将会介绍一个无监督的降维算法——t-SNE&#xff0c;其是一个常用的降维可视化工具&#xff0c;下面会记录一下李宏毅老师对其的原理介绍&#xff0c;然后我做了一个实验&#xff0c;用其来对手写数字&#xff08;MNIST数据集&#xff…

【论文学习之SNE-RoadSeg】跑通SNE-RoadSeg代码

0 序言 作为一个论文学习的小白&#xff0c;第一次去跑一篇论文代码可谓是下了老大功夫。从一开始的陌生&#xff0c;到现在逐渐熟练&#xff0c;对于如何正确跑通论文代码也有了较为清晰的方法步骤。这段时间跟着学长学习研究论文SNE-RoadSeg&#xff0c;所以接下来我将围绕此…

降维系列之 SNE与t-SNE

t-SNE是一种经典的降维和可视化方法&#xff0c;是基于SNE&#xff08;Stochastic Neighbor Embedding&#xff0c;随机近邻嵌入&#xff09;做的&#xff0c;要了解t-SNE就要先了解SNE。本文同样既是总结&#xff0c;又是读论文笔记。 SNE 随机近邻嵌入 SNE的的第一步是用条…

t-SNE算法详解

前言 此处只作为自己学习理解的笔记之用&#xff0c;转载于https://blog.csdn.net/sinat_20177327/article/details/80298645 t-SNE(t-distributed stochastic neighbor embedding)是用于降维的一种机器学习算法&#xff0c;是由 Laurens van der Maaten 和 Geoffrey Hinton在…

t-SNE数据降维可视化

t-SNE数据降维可视化 – 潘登同学的Machine Learning笔记 文章目录 t-SNE数据降维可视化 -- 潘登同学的Machine Learning笔记 t-SNE的基本思想SNE(Stochastic Neighbor Embedding)SNE的主要缺点距离不对称存在拥挤现象 如何确定 σ \sigma σ总结t-sne代码实现 对比t-sne与UMAP…

【机器学习】基于t-SNE数据可视化工程

一、说明 t-SNE (t-Distributed Stochastic Neighbor Embedding)是一种常用的非线性降维技术。它可以将高维数据映射到一个低维空间(通常是2D或3D)来便于可视化。Scikit-learn API提供TSNE类,以使用T-SNE方法可视化数据。在本教程中,我们将简要学习如何在 Python 中使用 TS…

t-SNE:如何理解与高效使用

摘要 尽管t-SNE对于可视化高维数据非常有用&#xff0c;但有时其结果可能无法解读或具有误导性。通过探索它在简单情况下的表现&#xff0c;我们可以学会更有效地使用它。 探索高维数据的一种流行方法是t-SNE&#xff0c;由 van der Maaten 和 Hinton[1] 在 2008 年提出。该技术…

How to Use t-SNE Effectively.(翻译:如何高效地使用t-SNE)

Translation: How to use t-SNE effectively 1. 这些超参数真的很重要2. 在t-SNE图中&#xff0c;簇大小没有任何意义3. 集群之间的距离可能没有任何意义4. 随机噪声并不总是随机的。5. 有时你会看到一些形状6. 对于拓扑&#xff0c;你可能需要多个绘图7. 结论 尽管t-SNE在可视…

t-SNE原理与推导

t-SNE(t-distributed stochastic neighbor embedding)是用于降维的一种机器学习算法&#xff0c;由 Laurens van der Maaten 和 Geoffrey Hinton在08年提出。t-SNE 作为一种非线性降维算法&#xff0c;常用于流形学习(manifold learning)的降维过程中并与LLE进行类比&#xff0…

t-SNE降维

t-SNE(t-distributed stochastic neighbor embedding)是用于降维的一种机器学习算法&#xff0c;是由 Laurens van der Maaten 和 Geoffrey Hinton在08年提出来。此外&#xff0c;t-SNE 是一种非线性降维算法&#xff0c;非常适用于高维数据降维到2维或者3维&#xff0c;进行可…

t-SNE 可视化

背景 t-SNE&#xff08;t-Distributed Stochastic Neighbor Embedding&#xff09;是一种非常流行的非线性降维技术&#xff0c;主要用来对高维数据进行可视化&#xff0c;了解和验证数据或者模型。t-SNE属于流行学习&#xff08;manifold learning&#xff09;&#xff0c;假…