java多线程(二一)并发协作生产者消费者设计模式
1.两个线程一个生产者一个消费者
需求情景
-
两个线程,一个负责生产,一个负责消费,生产者生产一个,消费者消费一个。
涉及问题 -
同步问题:如何保证同一资源被多个线程并发访问时的完整性。常用的同步方法是采用标记或加锁机制。
-
wait() / nofity() 方法是基类Object的两个方法,也就意味着所有Java类都会拥有这两个方法,这样,我们就可以为任何对象实现同步机制。
-
wait()方法:当缓冲区已满/空时,生产者/消费者线程停止自己的执行,放弃锁,使自己处于等待状态,让其他线程执行。
-
notify()方法:当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态。\
代码实现(共三个类和一个main方法的测试类)
package com.demo.ProducerConsumer;/*** 资源* @author lixiaoxi**/
public class Resource {/*资源序号*/private int number = 0;/*资源标记*/private boolean flag = false;/*** 生产资源*/public synchronized void create() {if (flag) {//先判断标记是否已经生产了,如果已经生产,等待消费;try {wait();//让生产线程等待} catch (InterruptedException e) {e.printStackTrace();}}number++;//生产一个System.out.println(Thread.currentThread().getName() + "生产者------------" + number);flag = true;//将资源标记为已经生产notify();//唤醒在等待操作资源的线程(队列)}/*** 消费资源*/public synchronized void destroy() {if (!flag) {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + "消费者****" + number);flag = false;notify();}
}
package com.demo.ProducerConsumer;/*** 生产者* @author lixiaoxi**/
public class Producer implements Runnable{private Resource resource;public Producer(Resource resource) {this.resource = resource;}@Overridepublic void run() {while (true) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}resource.create();}}
}
复制代码
package com.demo.ProducerConsumer;/*** 消费者* @author lixiaoxi**/
public class Consumer implements Runnable{private Resource resource;public Consumer(Resource resource) {this.resource = resource;}@Overridepublic void run() {while (true) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}resource.destroy();}}
}package com.demo.ProducerConsumer;public class ProducerConsumerTest {public static void main(String args[]) {Resource resource = new Resource();new Thread(new Producer(resource)).start();//生产者线程new Thread(new Consumer(resource)).start();//消费者线程}
}
二、多个线程,多个生产者和多个消费者的问题
需求情景
四个线程,两个个负责生产,两个个负责消费,生产者生产一个,消费者消费一个。
涉及问题
notifyAll()方法:当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的所有线程发出可执行的通知,同时放弃锁,使自己处于等待状态。
测试代码如下
package com.demo.ProducerConsumer;public class ProducerConsumerTest {public static void main(String args[]) {Resource resource = new Resource();new Thread(new Producer(resource)).start();//生产者线程new Thread(new Producer(resource)).start();//生产者线程new Thread(new Consumer(resource)).start();//消费者线程new Thread(new Consumer(resource)).start();//消费者线程}
}
通过以上打印结果发现问题
147生产了一次,消费了两次。
169生产了,而没有消费。
原因分析
当两个线程同时操作生产者生产或者消费者消费时,如果有生产者或消费者的两个线程都wait()时,再次notify(),由于其中一个线程已经改变了标记而另外一个线程再次往下直接执行的时候没有判断标记而导致的。
if判断标记,只有一次,会导致不该运行的线程运行了。出现了数据错误的情况。
解决方案
while判断标记,解决了线程获取执行权后,是否要运行!也就是每次wait()后再notify()时先再次判断标记。
代码改进(Resource中的 if -> while)
package com.demo.ProducerConsumer;/*** 资源* @author lixiaoxi**/
public class Resource {/*资源序号*/private int number = 0;/*资源标记*/private boolean flag = false;/*** 生产资源*/public synchronized void create() {while (flag) {//先判断标记是否已经生产了,如果已经生产,等待消费;try {wait();//让生产线程等待} catch (InterruptedException e) {e.printStackTrace();}}number++;//生产一个System.out.println(Thread.currentThread().getName() + "生产者------------" + number);flag = true;//将资源标记为已经生产notify();//唤醒在等待操作资源的线程(队列)}/*** 消费资源*/public synchronized void destroy() {while (!flag) {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + "消费者****" + number);flag = false;notify();}
}
再次发现问题
-
打印到某个值比如生产完187,程序运行卡死了,好像锁死了一样。
原因分析 -
notify:只能唤醒一个线程,如果本方唤醒了本方,没有意义。而且while判断标记+notify会导致”死锁”。
解决方案 -
notifyAll解决了本方线程一定会唤醒对方线程的问题。
最后代码改进(Resource中的 notify() -> notifyAll())
package com.demo.ProducerConsumer;/*** 资源* @author lixiaoxi**/
public class Resource {/*资源序号*/private int number = 0;/*资源标记*/private boolean flag = false;/*** 生产资源*/public synchronized void create() {while (flag) {//先判断标记是否已经生产了,如果已经生产,等待消费;try {wait();//让生产线程等待} catch (InterruptedException e) {e.printStackTrace();}}number++;//生产一个System.out.println(Thread.currentThread().getName() + "生产者------------" + number);flag = true;//将资源标记为已经生产notifyAll();//唤醒在等待操作资源的线程(队列)}/*** 消费资源*/public synchronized void destroy() {while (!flag) {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + "消费者****" + number);flag = false;notifyAll();}
}
以上就大功告成了,没有任何问题。
再来梳理一下整个流程。按照示例,生产者消费者交替运行,每次生产后都有对应的消费者,测试类创建实例,如果是生产者先运行,进入run()方法,进入create()方法,flag默认为false,number+1,生产者生产一个产品,flag置为true,同时调用notifyAll()方法,唤醒所有正在等待的线程,接下来如果还是生产者运行呢?这是flag为true,进入while循环,执行wait()方法,接下来如果是消费者运行的话,调用destroy()方法,这时flag为true,消费者购买了一次产品,随即将flag置为false,并唤醒所有正在等待的线程。这就是一次完整的多生产者对应多消费者的问题。
三、使用Lock和Condition来解决生产者消费者问题
上面的代码有一个问题,就是我们为了避免所有的线程都处于等待的状态,使用了notifyAll方法来唤醒所有的线程,即notifyAll唤醒的是自己方和对方线程。如果我需要只是唤醒对方的线程,比如:生产者只能唤醒消费者的线程,消费者只能唤醒生产者的线程。
在jdk1.5当中为我们提供了多线程的升级解决方案:
-
将同步synchronized替换成了Lock操作。
-
将Object中的wait,notify,notifyAll方法替换成了Condition对象。
-
可以只唤醒对方的线程。
完整代码:
package com.demo.ProducerConsumer;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** 资源* @author lixiaoxi**/
public class Resource1 {/*资源序号*/private int number = 0;/*资源标记*/private boolean flag = false;private Lock lock = new ReentrantLock();//使用lock建立生产者的condition对象private Condition condition_pro = lock.newCondition(); //使用lock建立消费者的condition对象private Condition condition_con = lock.newCondition(); /*** 生产资源*/public void create() throws InterruptedException {try{lock.lock();//先判断标记是否已经生产了,如果已经生产,等待消费while(flag){//生产者等待condition_pro.await();}//生产一个number++;System.out.println(Thread.currentThread().getName() + "生产者------------" + number);//将资源标记为已经生产flag = true;//生产者生产完毕后,唤醒消费者的线程(注意这里不是signalAll)condition_con.signal();}finally{lock.unlock();}}/*** 消费资源*/public void destroy() throws InterruptedException{try{lock.lock();//先判断标记是否已经消费了,如果已经消费,等待生产while(!flag){//消费者等待condition_con.await();}System.out.println(Thread.currentThread().getName() + "消费者****" + number);//将资源标记为已经消费flag = false;//消费者消费完毕后,唤醒生产者的线程condition_pro.signal();}finally{lock.unlock();}}
}package com.demo.ProducerConsumer;/*** 生产者* @author lixiaoxi**/
public class Producer1 implements Runnable{private Resource1 resource;public Producer1(Resource1 resource) {this.resource = resource;}@Overridepublic void run() {while (true) {try {Thread.sleep(10);resource.create();} catch (InterruptedException e) {e.printStackTrace();}}}}package com.demo.ProducerConsumer;/*** 消费者* @author lixiaoxi**/
public class Consumer1 implements Runnable{private Resource1 resource;public Consumer1(Resource1 resource) {this.resource = resource;}@Overridepublic void run() {while (true) {try {Thread.sleep(10);resource.destroy();} catch (InterruptedException e) {e.printStackTrace();}}}}package com.demo.ProducerConsumer;public class ProducerConsumerTest1 {public static void main(String args[]) {Resource1 resource = new Resource1();new Thread(new Producer1(resource)).start();//生产者线程new Thread(new Producer1(resource)).start();//生产者线程new Thread(new Consumer1(resource)).start();//消费者线程new Thread(new Consumer1(resource)).start();//消费者线程}
}
四、总结
1、如果生产者、消费者都是1个,那么flag标记可以用if判断。这里有多个,必须用while判断。
2、在while判断的同时,notify函数可能唤醒本类线程(如一个消费者唤醒另一个消费者),这会导致所有消费者忙等待,程序无法继续往下执行。使用notifyAll函数代替notify可以解决这个问题,notifyAll可以保证非本类线程被唤醒(消费者线程能唤醒生产者线程,反之也可以),解决了忙等待问题。
小心假死
生产者/消费者模型最终达到的目的是平衡生产者和消费者的处理能力,达到这个目的的过程中,并不要求只有一个生产者和一个消费者。可以多个生产者对应多个消费者,可以一个生产者对应一个消费者,可以多个生产者对应一个消费者。
假死就发生在上面三种场景下。假死指的是全部线程都进入了WAITING状态,那么程序就不再执行任何业务功能了,整个项目呈现停滞状态。
比方说有生产者A和生产者B,缓冲区由于空了,消费者处于WAITING。生产者B处于WAITING,生产者A被消费者通知生产,生产者A生产出来的产品本应该通知消费者,结果通知了生产者B,生产者B被唤醒,发现缓冲区满了,于是继续WAITING。至此,两个生产者线程处于WAITING,消费者处于WAITING,系统假死。上面的分析可以看出,假死出现的原因是因为notify的是同类,所以非单生产者/单消费者的场景,可以采取两种方法解决这个问题:
(1)synchronized用notifyAll()唤醒所有线程、ReentrantLock用signalAll()唤醒所有线程。
(2)用ReentrantLock定义两个Condition,一个表示生产者的Condition,一个表示消费者的Condition,唤醒的时候调用相应的Condition的signal()方法就可以了。
相关文章:
java多线程(二一)并发协作生产者消费者设计模式
1.两个线程一个生产者一个消费者 需求情景 两个线程,一个负责生产,一个负责消费,生产者生产一个,消费者消费一个。 涉及问题 同步问题:如何保证同一资源被多个线程并发访问时的完整性。常用的同步方法是采用标记或加…...
Win YAPI + Jenkins 实现接口自动化测试
自动化测试 传统的接口自动化测试成本高,大量的项目没有使用自动化测试保证接口的质量,仅仅依靠手动测试,是非常不可靠和容易出错的。 为了解决这个问题,使用YAPI接口自动化测试功能,只需要配置每个接口的入参和对 RE…...
【计算机视觉 自然语言处理】什么是多模态?
文章目录一、多模态的定义二、多模态的任务2.1 VQA(Visual Question Answering)视觉问答2.2 Image Caption 图像字幕2.3 Referring Expression Comprehension 指代表达2.4 Visual Dialogue 视觉对话2.5 VCR (Visual Commonsense Reasoning) 视觉常识推理…...
2023百度面试真题
【百度】面试真题: 1、SpingBoot 也有定时任务?是什么注解? 在 SpringBoot 中使用定时任务主要有两种不同的方式,一个就是使用 Spring 中的Scheduled 注解,另一个则是使用第三方框架 Quartz。 使用 Spring 中的 Sch…...
MAC(m1)-VMWare Fushion安装Windows11
镜像下载地址:登录 账号:11360XXXXX@qq.com 密码:ZXXXSXX19XX 参考:VMware fusion虚拟机安装Win10系统的详细教程_IT大力水手的博客-CSDN博客_vmware fusion安装 uefi和bios有什么区别?uefi和bios的区别详细分析 _ 电脑系统城 设置密码...
HTML与CSS简介
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 HTML与CSS简介前言一、HTML简单梳理1.HTML文件的书写规范2.常用标签介绍二、CSS简单梳理1、CSS选择器前言 页面由三部分内容组成!分别是内容(结构&am…...
基于Java开发幼儿园管理系统项目教程(附源码)
文章目录你将会学到:适合人群:课程目标:课程简介:软件架构开发环境运行截图你将会学到: 掌握市面上主流框架SpringMvc、Spring、MyBatis、SpringBoot实战开发技巧提升学员代码编码能力和实战项目编码经验熟悉企业级项…...
第一次运行vue遇到的问题
1.vue无法识别https://blog.csdn.net/weixin_61634408/article/details/1265897982.yarn serve问题https://blog.csdn.net/fangxuan1509/article/details/104711690/3.关闭控制台报错检查(每次vue-rounter必须用)vue.config,js,的module.exports 中添加l…...
Clickhouse数据去重
1. Hive去重 先以两个简单的sql启发我们的话题 select count(distinct id)from order_combine;select count(id) from (select id from order_combine group by id ) t;从执行日志当中我们可以看到二者的差异(只摘取关键部分) # distinctStage-Stage…...
精讲typescript从入门到入土
前言 TypeScript是一种由Microsoft开发的编程语言,它是JavaScript的超集,意味着它可以编写与JavaScript完全兼容的代码,并且可以扩展其功能。TypeScript的主要目标是提供类型安全性和更好的可维护性,使得开发大型复杂应用程序更加…...
typora-beta-0.11.18版本又提示过期的解决方案
很实用,所以照搬一下下面的作者的回答,省得以后再找~~~ 知乎的作者来源如下: 作者:吴小皓 链接:typora打开报错:This beta version of Typora is expired, please download and install a newer version …...
WebUI自动化测试框架搭建(二十)-优化:测试对象无法连接或出现异常时,请更新本文作为测试对象
(二十)-测试对象无法连接或出现异常时,请更新本文作为测试对象 1 测试对象说明2 源代码下载3 学生管理系统配置安装3.1 解压打开3.2 安装依赖3.3 安装mysql数据库3.4 修改项目数据库配置3.4 安装数据库连接工具Navicat3.5 导入数据库脚本4 运行学生管理系统5 系统查看1 测试…...
【FATE联邦学习】standalone版Fateboard修改配置
背景&做法 很多其他程序(比如vscode的code server)也会使用这个 127 0 0 1:8080 socket进行通信,这样就没办法远程用vscode去开发了,所以需要修改下Fateboard的socket配置。官方文档中也给出了如何修改配置 The default data…...
分享一个应急响应web日志:access.log文件分析小工具
有时做应急响应的时候,需要提取web日志如access.log日志文件来分析系统遭受攻击的具体原因,由于开源的工具并不是很好用,所以自己用Python3写了一个简单的日志分析工具。先介绍一下access.log日志access.log日志文件记录了所有目标对Web服务器…...
windows注册服务非常实用
方式一:使用Windows自带的sc命令 1、使用管理员权限打开cmd窗口 2、注册服务命令: sc create 服务名 binpath 程序所在路径 type own start auto displayname 服务显示名称 sc create redis binpath d:\tools\redis-x64-5.0.14\redis-server.exe type …...
蓝桥dfs专题
1、dfs 路径打印 小明冒充X星球的骑士,进入了一个奇怪的城堡。 城堡里边什么都没有,只有方形石头铺成的地面。 假设城堡地面是 n x n 个方格。【如图1.png】所示。 按习俗,骑士要从西北角走到东南角。 可以横向或纵向移动,但不能…...
[ 网络 ] 应用层协议——HTTPS协议原理
目录 1.HTTPS是什么 2.加密技术 2.1什么是加密 2.2为什么要加密 2.3加密处理防止被窃听 3.常见的加密方式 对称加密 非对称加密 4.数据摘要&&数据指纹 5.数字签名 6.HTTPS的工作过程探究 方案1——只是用对称加密 方案2——只进行非对称加密 方案3——双方…...
http协议如何操作
、HTTP协议(超文本传输协议) 1.1、http协议是一个基于“请求与响应”模式的、无状态的应用层协议。 http协议采用URL作为定位网络资源的标识。 1.2、URL格式 http://host[:port][path] host:合法的Internet主机域名或IP地址 port:端口号…...
ESP Insights 系列文章
ESP Insights 系列 #1 | 远程查看设备信息,快速解决固件问题 ESP Insights 是一个可远程查看设备固件运行状态和日志的平台,能够帮助开发人员快速定位并解决固件问题。 ESP Insights 系列 #2 | 新增功能 最新版本优化了用户界面、修复了系统稳定性&am…...
如何提高爬虫工作效率
单进程单线程爬取目标网站太过缓慢,这个只是针对新手来说非常友好,只适合爬取小规模项目,如果遇到大型项目就不得不考虑多线程、线程池、进程池以及协程等问题。那么我们该如何提升工作效率降低成本? 学习之前首先要对线程&#…...
React结合Drag API实现拖拽示例详解
Drag API React中的Drag API是用于实现拖放功能的API。该API由React DnD库提供,可用于实现拖放操作,例如将元素从一个位置拖动到另一个位置。 React DnD库提供了两种Drag API:基于HTML5的拖放API和自定义实现的拖放API。 基于HTML5的拖放AP…...
【华为OD机试java、python、c++、jsNode】新学校选址(100%通过+复盘思路)
代码请进行一定修改后使用,本代码保证100%通过率。本文章提供java、python、c++、jsNode四种代码。复盘思路在文章的最后 题目描述 为了解新学期学生暴涨的问题,小乐村要建立所新学校, 考虑到学生上学安全问题,需要所有学生家到学校的距离最短。 假设学校和所有学生家都走在…...
Nacos配置中心,分组配置参考,以及python、go、bash客户端连接获取
Nacos使用说明 nacos官方网站 https://nacos.io/zh-cn/docs/v2/what-is-nacos.html 1、基本配置说明 nacosIP地址:http://xxxxx:8848/nacos/ 服务管理端登录账号:nacos XXX Java最小配置,其他客户端可参考,配置可对应到第三章…...
node-red中有关用户登录,鉴权,权限控制的流程解析
前言 默认地,node-red编辑器可以被任何访问的用户操作,包括修改节点,流数据,重新部署流。 这种默认的部署方式只适用于运行在可靠的网络中。下面我就给大家介绍一下,在公网上部署node-red后,如何对其进行安全加固和权限验证。 主要分为三部分 开启https权限保护编辑器和…...
MQTT协议-使用CONNECT报文连接阿里云
使用网络调试助手发送CONNECT报文连接阿里云 参考:https://blog.csdn.net/daniaoxp/article/details/103039296 在前面文章介绍了如何组装CONNECT报文,以及如何计算剩余长度 CONNECT报文:https://blog.csdn.net/weixin_46251230/article/d…...
每日学术速递3.8
CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Unleashing Text-to-Image Diffusion Models for Visual Perception 标题:释放用于视觉感知的文本到图像扩散模型 作者:Wenliang Zhao, Yongming Rao, Zuya…...
测牛学堂:软件测试之接口测试理论基础总结
接口概念 接口:系统之间数据交互的通道。 这个系统,可以是外部和内部,也可以是两个内部系统之间的通道。 比如我们前端的登录信息,主要是用户名和密码,它通过接口传递给后端,后端校验以后,把结…...
基于土壤数据与机器学习算法的农作物推荐算法代码实现
1.摘要 近年来,机器学习方法在农业领域的应用取得巨大成功,广泛应用于科 学施肥、产量预测和经济效益预估等领域。根据土壤信息进行数据挖掘,并在此基础上提出区域性作物的种植建议,不仅可以促进农作物生长从而带来经济效益&#…...
python中html必备基础知识
<!DOCTYPE html>此标签表示这是一个html文件<heml lang"en">向搜索引擎表示该页面是html语言,并且语言为英文网站,其"lang"的意思就是“language”,语言的意思,而“en”即表示English<head>…...
【专项训练】前言:刻意练习,不断的过遍数才是王道
如何精通一个领域? 拆分知识点刻意练习:每个区域的基础动作分解训练和反复刻意练习反馈(主动反馈、被动反馈、及时反馈)任何知识体系都是一颗树,一定要梳理成思维导图,明确知识与知识之间的关系! 通过7-8周密集训练,练好基本功,彻底攻克LeetCode! 严格执行五毒神掌!…...
建立一个商城网站/比较好的友链平台
如果你在用PHP, 你可能需要用到cURL, 这是其中最流行的插件. PHP CURL插件需要通过libcurl来实现, Daniel Stenberg创建的一个库, 能够和许多不同类型协议的web服务器进行通信连接。架设你已经安装了LAMP. 需要安装cURL 在你的Linux计算机上,比如Ubuntu,运行一下命令…...
慈溪想做网站的公司/快速推广
Python 2.x的官方支持要到2020就截止了,djagon 2.0以上的版本则明确不支持python 2.7版本,需要python 3.5,而有的项目有的需要兼容性,比如要连接Mysql数据库的第三方库, Python 2.x使用的是MySQLdb模块,Pyt…...
服装设计师常用网站/长春seo整站优化
1第五人格1相信很多玩家都知道每个赛季都会公布一些角色的推演故事,玩家把这些有趣的故事串联起来就可以发现庄园中的秘密,而在全新的第九赛季,官方开放了两个求生者的推演任务。他们分别是杂技演员和野人,趣游君也在第一时间完成…...
seo网站推广是什么/伟哥seo博客
前言: 我们知道es5之前共有5种数据类型,分别是:undefined,null,string,boolean,number; 但在es6中新增了一种Smybol数据类型,它是一种类似于字符串的数据类型,用来保证每个属性的名字都是独一无二的. 1.声明方法如下: let s Symbol(); console.log(typeof s);//symbol类型注意…...
vps wordpress域名/seo排名怎么优化软件
硬盘是电脑中很重要的一个部分,我们工作中的数据绝大部份都保存在硬盘中,因此硬盘的维护是很重要的一个环节,在日常使用中,我们应该遵循以下的几点来保护硬盘: 1、 使用过程不要移动电脑; 2、 移动硬盘时轻…...
云南旅游网站建设/自己怎么优化关键词
一.用lua开发游戏打包之前要添加底层代码:选择项目右键单击选择Cocos Tools->Add Native Codes A完成后Close退出生成的源代码在以下目录,用xcode打开连上手机就能运行cocos->PlaneLua->frameworks->runtime-src->proj.ios_mac->mac-&…...