多线程JUC:等待唤醒机制(生产者消费者模式)

👨‍🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
🌌上期文章:多线程&JUC:解决线程安全问题——synchronized同步代码块、Lock锁
📚订阅专栏:多线程&JUC
希望文章对你们有所帮助

等待唤醒机制(生产者消费者模式)

  • 等待唤醒机制
  • 等待唤醒机制的实现
    • 消费者代码实现
    • 生产者代码实现
  • 阻塞队列实现等待唤醒机制

等待唤醒机制

等待唤醒机制也叫做生产者消费者模式,打破了以前线程间执行的随机性,生产者消费者模式能够使得线程之间是轮流运行的。是一个非常经典的多线程协作的模式。
对于两条线程,其中一条为生产者,另一条为消费者,大家都是学习过操作系统的,原理多少还是记得一些的。

对于等待唤醒机制,其只有2种情况:

1、消费者等待:若没有可以被消费者消费的数据,那么消费者就是进入wait状态,这时候生产者就可以抢占CPU生产数据,接着notify(唤醒)消费者
2、生产者等待:若已经有数据供给消费者消费,则生产者进入wait状态,消费者抢占CPU消费数据,接着notify(唤醒)生产者

在这其中可能会涉及到的方法:

方法名称说明
void wait()当前线程等待,直到被其他线程唤醒
void notify()随机唤醒单个线程
void notifyAll()唤醒所有线程

等待唤醒机制的实现

消费者代码实现

消费者和生产者中间有一个控制他们执行相应操作的核心,视为Controller,记录一些状态变量和锁对象:

public class Controller {/*** 控制消费者和生产者的执行*///表示是否有数据 0:没有 1:有public static int flag = 0;//消费者最多可以消费的数据量public static int count = 10;//锁对象public static Object lock = new Object();
}

接着实现消费者的逻辑:

public class Consumer extends Thread{@Overridepublic void run() {while(true){synchronized (Controller.lock) {if(Controller.count == 0){//消费者已经消费量了10次,退出break;}else{//先判断有无可以消费的数据if(Controller.flag == 0) {//若无,等待//用lock调用wait方法,使得当前线程与锁进行绑定,之后唤醒就唤醒这些被绑定了的线程try {Controller.lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}else{//若有,消费System.out.println("正在消费,还可以消费" + --Controller.count + "个");//消费完后唤醒生产者,唤醒绑定在这把锁上的所有线程Controller.lock.notifyAll();//修改控制中心的状态Controller.flag = 0;}}}}}
}

生产者代码实现

public class Producer extends Thread{@Overridepublic void run() {while (true){synchronized (Controller.lock){if(Controller.count == 0){break;}else{if(Controller.flag == 1){//已经有供给消费者进行消费的数据try {Controller.lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}else{System.out.println("成功生产");Controller.lock.notifyAll();Controller.flag = 1;}}}}}
}

最后编写测试类代码验证:

public class ThreadDemo {public static void main(String[] args) {//创建线程对象Producer producer = new Producer();Consumer consumer = new Consumer();//给线程设置名字producer.setName("生产者");consumer.setName("消费者");//开启线程producer.start();consumer.start();}
}

阻塞队列实现等待唤醒机制

何为阻塞队列?其实就是连接生产者和消费者的一个队列,管理着数据,分别供消费者take和生产者的put,如果put不进去或者take不出,则说明队列满了或者空了,这时候就会进入阻塞状态。

阻塞队列BlockingQueue本身实现了Iterable、Collection、Queue的接口,无法直接实例化,但是其具有2个实现类:

1、ArrayBlockingQueue:底层为数组,有界
2、LinkedBlockingQueue:底层为链表,无界(不是真正的无界,最大为int的最大范围,只是无须指定范围)

利用阻塞队列来实现是很便捷的,因为我们可以查看put和take方法的底层,可以发现这两个方法是自带锁的,所以我们在实现生产者和消费者的时候无须自己上锁,否则反而会容易因为锁的嵌套而发生死锁。
在这里插入图片描述
在这里插入图片描述
生产者代码:

public class Producer extends Thread{ArrayBlockingQueue<String> queue;public Producer(ArrayBlockingQueue<String> queue) {this.queue = queue;}@Overridepublic void run() {while (true) {//直接不断的把数据放进阻塞队列,如果满了它自己会阻塞try {queue.put("数据");System.out.println("消费者生产了一个数据到阻塞队列");} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

消费者代码:

public class Consumer extends Thread{ArrayBlockingQueue<String> queue;public Consumer(ArrayBlockingQueue<String> queue) {this.queue = queue;}@Overridepublic void run() {while (true){try {String take = queue.take();System.out.println(take);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

测试类:

public class ThreadDemo {/*** 使用阻塞队列实现等待唤醒机制,要保证生产者和消费者用的是同一个阻塞队列*/public static void main(String[] args) {//创建一个可以存放1个数据的阻塞队列ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);//创建生产者和消费者对象,并把阻塞队列传递过去,使得它们使用同一个阻塞队列Producer producer = new Producer(queue);Consumer consumer = new Consumer(queue);producer.setName("生产者");consumer.setName("消费者");producer.start();consumer.start();}
}

在这里插入图片描述
最后显示可能会重复打印数据,这是因为输出的语句没有放在锁里面,锁可以执行的put和take已经写死了,但是并不影响我们实际数据的并发安全性,只是不方便我们的观察罢了。

至此,阻塞队列实现等待唤醒机制的demo已经跑通了,阻塞队列底层的执行实际上是异步的,可以解决在实际生产环境中的超卖问题,具体可以看我之前的文章:
Redis:原理速成+项目实战——Redis实战9(秒杀优化)

当然,主流的方法还是使用消息队列RabbitMQ或Kafka,这个大家可以自行去了解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/675193.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Kubernetes实战(二十七)-HPA实战

1 HPA简介 HPA 全称是 Horizontal Pod Autoscaler&#xff0c;用于POD 水平自动伸缩&#xff0c; HPA 可以 基于 POD CPU 利用率对 deployment 中的 pod 数量进行自动扩缩容&#xff08;除了 CPU 也可以基于自定义的指标进行自动扩缩容&#xff09;。pod 自动缩放不适用于无法…

NGINX upstream、stream、四/七层负载均衡以及案例示例

文章目录 前言1. 四/七层负载均衡1.1 开放式系统互联模型 —— OSI1.2 四/七层负载均衡 2. Nginx七层负载均衡2.1 upstream指令2.2 server指令和负载均衡状态与策略2.2.1 负载均衡状态2.2.2 负载均衡策略 2.3 案例 3. Nginx四层负载均衡的指令3.1 stream3.2 upstream指令3.3 四…

深入理解ES的倒排索引

目录 数据写入过程 词项字典 term dictionary 倒排表 posting list FOR算法 RBM算法 ArrayContainer BitMapContainer 词项索引 term index 在Elasticsearch中&#xff0c;倒排索引的设计无疑是惊为天人的&#xff0c;下面看下倒排索引的结构。 倒排索引分为词项索引【…

JS中常用占位符使用方法详解_ |%s|%d|%f|%o|%O|%c|

在 JavaScript 中&#xff0c;%s 是一种字符串格式化占位符&#xff0c;用于将字符串插入到另一个字符串中的指定位置。这种方法基于 C 语言的 printf() 函数&#xff0c;但在 JavaScript 中有一些变化。 在 JavaScript 中&#xff0c;%s 可以接受任何类型的值&#xff0c;并将…

上市公司人工智能转型指数及55个工具变量汇总数据集(2024.2月更新)

一、“智能化转型”发文趋势和主题分布 二、数据来源 上市公司年报、官网&#xff0c;中国知网及各期刊官网等三、时间跨度 工具变量&#xff1a;2022-2024年&#xff1b; 上市公司人工智能转型指数&#xff1a;2007-2021年四、数据范围 中国A股上市公司五、数据展示 序号…

一键部署自动化运维工具spug

简介 Spug是面向中小型企业设计的轻量级无Agent的自动化运维平台&#xff0c;整合了主机管理、主机批量执行、主机在线终端、应用发布部署、在线任务计划、配置中心、监控、报警等一系列功能。 部署 1.创建目录 mkdir -p /opt/spug/{mysql,service,repos} 2.进入目录 cd /o…

Modern C++ 内存篇1 - allocator

1. 前言 从今天起我们开始内存相关的话题&#xff0c;内存是个很大的话题&#xff0c;一时不知从何说起。内存离不开allocator&#xff0c;我们就从allocator开始吧。allocator目前有两种&#xff1a;std::allocator, std::pmr::polymorphic_allocator&#xff0c;各有优缺点。…

使用 matplotlib 探究Java HashCode中乘数和质数的影响

在Java中,hashCode()方法被广泛应用于散列实现,特别是在集合类中。这个方法用于返回对象的哈希码值,通常用于确定对象在哈希表中的存储位置。在这个探究中,我们将深入研究hashCode()方法中两个关键参数:乘数(multiplier)和质数(prime),探究它们对散列结果的影响。 代…

Vue源码系列讲解——虚拟DOM篇【二】(Vue中的DOM-Diff)

目录 1. 前言 2. patch 3. 创建节点 4. 删除节点 5. 更新节点 6. 总结 1. 前言 在上一篇文章介绍VNode的时候我们说了&#xff0c;VNode最大的用途就是在数据变化前后生成真实DOM对应的虚拟DOM节点&#xff0c;然后就可以对比新旧两份VNode&#xff0c;找出差异所在&…

docker 基于容器创建本地web容器化镜像

一、docker 基于容器创建本地web容器化镜像 1、启动指定buysbox 镜像 docker run --name b1 -it busybox:latest 2、创建目录&#xff0c;并创建html mkdir -p /data/html vi index.html 内容自定义例如&#xff1a;<h1>welcome to busybox<h1> 3、新增窗口&am…

ubuntu22.04 安装部署05:禁用默认显卡驱动

一、相关文章 ubuntu22.04安装部署03&#xff1a; 设置root密码-CSDN博客 《ubuntu22.04装部署01&#xff1a;禁用内核更新》 《ubuntu22.04装部署02&#xff1a;禁用显卡更新》 二、场景说明 Ubuntu22.04 默认显卡驱动&#xff0c;如果安装cuda&#xff0c;需要单独安装显…

Android开发 button 按钮点击两次 响应onclick方法

问题 Android开发 button 按钮点击两次 响应onclick方法 详细问题 笔者xml代码 <!-- 一个按钮 --> <Button android:id"id/button1" android:layout_width"wrap_conten…

模型环境备份

很多时候&#xff0c;在调试新环境的时候&#xff0c;需要对环境进行保存备份&#xff0c;然后为后面的环境复原做好准备 综合建议 备份环境&#xff1a;在进行这些更改之前&#xff0c;如果您在使用虚拟环境&#xff08;强烈推荐&#xff09;&#xff0c;可以考虑先导出当前…

Conda历史版本下载地址和python对应关系

一、前言 因为Conda安装版本问题&#xff0c;带来了很多问题&#xff0c;虽然不能直接确定二者之间的关系&#xff0c;但是安装指定版本的conda,确实是一个比较好的方法。特此记忆。 二、下载地址 下载最新版本&#xff1a;Free Download | Anaconda 下载历史版本&#xff…

Kafka系列之:Kafka集群同时设置基于时间和日志大小两种方式保存Topic的数据

Kafka系列之:Kafka集群同时设置基于时间和日志大小两种方式保存Topic的数据 一、基于日志大小二、基于时间大小三、参数设置四、设置命令一、基于日志大小 "log.retention.bytes"是Apache Kafka中的一项配置参数,用于指定每个日志段文件的最大大小。当日志段文件的…

利用低代码 BI 平台获得竞争优势:实现数据分析与业务决策的革新

介绍 疫情迫使企业优先考虑数字化转型。由于公司被迫参加计划外的数字化速成课程&#xff0c;这种文化转变将数字技术的采用加速了数年。 转向数字解决方案已成倍增加了跨行业生成的数据量。大量数据可以更好地了解运营、客户和市场&#xff0c;还可以推动任何组织的创新。 …

Xcode配置GLFW GLAD (MAC)

这里的GLFW用的是静态链接 博主反复修改&#xff0c;实在是没能找到为什么用动态会出现线程报错 下载GLAD:版本我一般是选倒数第二新&#xff0c;profile记得选core 点击GENRATE 点glad.zip获得下载 下载GLFW 点击download 最后&#xff0c;将两个文件都放到项目里面去 打开…

DataX源码分析 TaskGroupContainer

系列文章目录 一、DataX详解和架构介绍 二、DataX源码分析 JobContainer 三、DataX源码分析 TaskGroupContainer 四、DataX源码分析 TaskExecutor 五、DataX源码分析 reader 六、DataX源码分析 writer 七、DataX源码分析 Channel 文章目录 系列文章目录TaskGroupContainer初始…

形态学操作之开操作与闭操作的python实现——数字图像处理

原理 图像处理中的开操作&#xff08;Opening&#xff09;和闭操作&#xff08;Closing&#xff09;是形态学&#xff08;Morphological&#xff09;操作的两个基本类型&#xff0c;它们都是基于膨胀&#xff08;Dilation&#xff09;和腐蚀&#xff08;Erosion&#xff09;操…

JAVA面试题11

什么是Java的访问修饰符&#xff0c;并列出它们的作用。 Java的访问修饰符包括public、private、protected和默认。它们的作用如下&#xff1a; public: 可以被任何其他类访问。 private: 只能被所在类访问&#xff0c;其他类无法访问。 protected: 可以被所在类和同一个包中的…