Java生产消费者模型——代码解析
我们将生产者、消费者、库存、和调用线程的主函数分别写进四个类中,通过抢夺非线程安全的数据集合来直观的表达在进行生产消费者模型的过程中可能出现的问题与解决办法。
我们假设有一个生产者,两个消费者来共同抢夺库存里的资源,而生产者和消费者都以线程来实现。
库存对象只有是唯一的才会出现抢夺一个资源的可能,所以为了使库存对象是唯一的,我们可以使用两种方法实现,单例模式和通过生产者和消费者的构造函数参数来初始化。
本次举例使用的是构造函数的方法,但代码中也注释出了单例模式的写法与使用。
先创建一个简单的生产消费者模型,查看它的运行结果。
库存类:
package producterac; import java.util.ArrayList; public class WareHouse { //存放非线程安全的数组的集合
private ArrayList<String> list = new ArrayList<String>(); /*
* //创建单例模式使生产消费者操作的是同一库存对象
* private WareHouse() {}
* //建立静态对象以在初始化的时候建立仅一个库存对象
* private static WareHouse wh = new WareHouse();
*
* //将方法设置为静态是因为在无法new库存对象的情况下,
* //我们可以通过将方法设定为静态来直接通过类名调用静态方法
* public static WareHouse getInstance() {
* return wh;
* }
*/ //写生产者操作仓库的方法
public void add() {
if(list.size() < 20) {
list.add("一个数据");
}else {
//数据存够之后直接返回,不运行存储数据的操作
return;
}
} //写消费者操作仓库的操作
public void get() {
//判断集合中是否还有数据可以取出
//如果不判断会造成集合越界
if(list.size() > 0) {
list.remove(0);
}else {
return;
}
} }
- 生产者类:
package producterac; public class Producter extends Thread{ private String pName; //我们要使生产者和消费者操控同一个库存对象
//也可以使用单例模式来建立库存对象
private WareHouse wh;
public Producter(String pName,WareHouse wh) {
this.pName = pName;
this.wh = wh;
} //重写run方法
public void run() {
while(true) {
wh.add();
System.out.println("生产者"+pName+"添加了一个货物");
try {
//使线程等待一会儿
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
- 消费者类:
package producterac; public class Consumer extends Thread{ private String cName; //获取库存对象
/* private WareHouse wh = WareHouse.getInstance(); */ //我们要使生产者和消费者操控同一个库存对象
//也可以使用单例模式来建立库存对象
private WareHouse wh;
public Consumer(String cName,WareHouse wh) {
this.cName = cName;
this.wh = wh;
} //重写run方法
public void run() {
while(true) {
wh.get();
System.out.println("消费者"+cName+"拿走了一个货物");
try {
//使线程等待一会儿
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
- 主函数类:
package producterac; public class Main { public static void main(String[] args) { WareHouse wh = new WareHouse();
Producter p1 = new Producter("1", wh);
Consumer c1 = new Consumer("1", wh);
Consumer c2 = new Consumer("2", wh); p1.start();
c1.start();
c2.start();
}
}
部分运行结果:
我们看到出现 java.lang.ArrayIndexOutOfBoundsException异常,说明消费者在拿走货物的时候集合越界没有拿到,所以出现了异常。
即使我们在库存的get()方法中判断了集合是否为空,但也还是出现了异常。原因是因为在两个线程同时访问一个对象的时候,有可能当线程1刚判断完集合不为空进入了if循环但还没有拿走货物的情况下,线程2也进行了get()方法先线程1一步拿走了最后的一个货物,然后当线程1想拿走货物的时候集合里已经没有了,这种情况下就会发生上述异常。
这就造成了线程抢夺资源时非安全的问题,那么我们可以将库存对象使用线程锁synchronized锁起来,这样在一个消费者访问库存对象的时候其他消费者无法访问库存对象,从而解决集合越界问题,使线程安全。
- 修改过的库存类(加入了synchronized修饰符的add()和get()方法):
//写生产者操作仓库的方法
public synchronized void add() {
if(list.size() < 20) {
list.add("一个数据");
}else {
//数据存够之后直接返回,不运行存储数据的操作
return;
}
} //写消费者操作仓库的操作
public synchronized void get() {
//判断集合中是否还有数据可以取出
//如果不判断会造成集合越界
if(list.size() > 0) {
list.remove(0);
}else {
return;
}
}
使用synchronized修饰符修饰库存方法之后就不会报错了!
我们也可以将return替换为wait()方法让线程等待,将编写的生产消费者模型中的return修改为wait()。
- 修改过的库存类:
//写生产者操作仓库的方法
public synchronized void add() {
if(list.size() < 20) {
list.add("一个数据");
}else {
try {
//这个this指的是访问库存对象的线程wait,不是库存对象wait
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} //写消费者操作仓库的操作
public synchronized void get() {
//判断集合中是否还有数据可以取出
//如果不判断会造成集合越界
if(list.size() > 0) {
list.remove(0);
}else {
try {
//这个this指的是访问库存对象的线程wait,不是库存对象wait
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行结果:
我们会发现到最后所有的线程都会处于wait等待状态,运行到最后没有线程在执行了。所以我们需要在其中一个线程等待的时候将其他线程继续唤醒,保持系统的运行。
唤醒线程可以使用notify/notifyAll()方法。
- 再次修改后的库存类:
//写生产者操作仓库的方法
public synchronized void add() {
if(list.size() < 20) {
list.add("一个数据");
}else {
try {
//因为我们无法知道哪个线程是消费者线程,所以我们要将线程全部唤醒
this.notifyAll();
//这个this指的是访问库存对象的线程wait,不是库存对象wait
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} //写消费者操作仓库的操作
public synchronized void get() {
//判断集合中是否还有数据可以取出
//如果不判断会造成集合越界
if(list.size() > 0) {
list.remove(0);
}else {
try {
//因为我们无法知道哪个线程是生产者线程,所以我们要将线程全部唤醒
this.notifyAll();
//这个this指的是访问库存对象的线程wait,不是库存对象wait
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行成功!说明我们这时候真正地实现了简单的生产消费者模型。
附:如果将完成的生产消费者模型中add()和get()方法的synchronized修饰符去掉,会发生如下错误。
将synchronized修饰符去掉后,发生了java.lang.IllegalMonitorStateException异常,原因是当线程1进入else要执行wait()方法的那个时刻,线程2也进入了库存对象中,致使当wait()方法真正执行的时候wait的是线程2而不是线程1,发生这种情况的时候就会发生上述异常。
Java生产消费者模型——代码解析的更多相关文章
- JAVA实现生产消费者模型
前言 最近面试比较多,发现生产消费者模型在各公司面试的过程中问的还是比较多的,记录一下常见JAVA实现生产者消费模型的代码 思路 我们通过三种模式来实现 通过wait和notify 通过Lock和Co ...
- Linux——多线程下解决生产消费者模型
我们学习了操作系统,想必对生产消费者问题都不陌生.作为同步互斥问题的一个经典案例,生产消费者模型其实是解决实际问题的基础模型,解决很多的实际问题都会依赖于它.而此模型要解决最大的问题便是同步与互斥.而 ...
- Python进阶----进程之间通信(互斥锁,队列(参数:timeout和block),), ***生产消费者模型
Python进阶----进程之间通信(互斥锁,队列(参数:timeout和block),), ***生产消费者模型 一丶互斥锁 含义: 每个对象都对应于一个可称为" 互斥锁&qu ...
- Python之queue模块以及生产消费者模型
队列 队列类似于一条管道,元素先进先出,进put(arg),取get() 有一点需要注意的是:队列都是在内存中操作,进程退出,队列清空,另外,队列也是一个阻塞的形态. 队列分类 队列有很多中,但都依赖 ...
- Python并发编程04 /多线程、生产消费者模型、线程进程对比、线程的方法、线程join、守护线程、线程互斥锁
Python并发编程04 /多线程.生产消费者模型.线程进程对比.线程的方法.线程join.守护线程.线程互斥锁 目录 Python并发编程04 /多线程.生产消费者模型.线程进程对比.线程的方法.线 ...
- Python - Asyncio模块实现的生产消费者模型
[原创]转载请注明作者Johnthegreat和本文链接 在设计模式中,生产消费者模型占有非常重要的地位,这个模型在现实世界中也有很多有意思的对应场景,比如做包子的人和吃包子的人,当两者速度不匹配时, ...
- Java生产者消费者模型
在Java中线程同步的经典案例,不同线程对同一个对象同时进行多线程操作,为了保持线程安全,数据结果要是我们期望的结果. 生产者-消费者模型可以很好的解释这个现象:对于公共数据data,初始值为0,多个 ...
- java生产者消费者问题代码分析
作者要的是一个生产者生成,接着必须有一个消费者消费,那这不是需要单线程吗?或者使用1个大小的阻塞队列.所以只谈论问题本身,不谈论好不好. 具体代码: import java.util.concurre ...
- Python——Queue模块以及生产消费者模型
1.了解Queue Queue是python标准库中的线程安全的队列(FIFO)实现,提供了一个适用于多线程编程的先进先出的数据结构,即队列,用来在生产者和消费者线程之间的信息传递 |queue.Qu ...
随机推荐
- Numpy | 05 创建数组
ndarray 数组除了可以使用底层 ndarray 构造器来创建外,也可以通过以下几种方式来创建. 一.numpy.empty numpy.empty 方法用来创建一个指定形状(shape).数据类 ...
- 【POJ3278】Catch That Cow
本题传送门 本题知识点:宽度优先搜索 题意很简单,我们把FJ与奶牛看作是在一条数轴上的点,奶牛固定在K点,FJ的移动有三种,分别是向前一格,向后一格以及跳到当前格的两倍去.问FJ花费最少的时间到达奶牛 ...
- eclipse tomcat 热加载 免除重启
Tomcat的热部署(以后就不用重起了) 1. tomcat上的部署问题,有时候也是个麻烦的问题,要是不采用热部署,我们就只能每次对原来的文件做一次改动的时候就要重新部署, 而每次重新部署都 ...
- NIOBuffer 缓冲区
Java NIO 由以下几个核心部分组成: Channels Buffers Selectors 虽然Java NIO 中除此之外还有很多类和组件,但是,Channel,Buffer 和 Select ...
- Net core学习系列(八)——Net Core日志
一.简介# 日志组件,作为程序员使用频率最高的组件,给程序员开发调试程序提供了必要的信息.ASP.NET Core中内置了一个通用日志接口ILogger,并实现了多种内置的日志提供器,例如 Conso ...
- java对象生成随意属性值
public class RandomObjectValue { public static <T> T getObject(Class<?> clazz) { T t = n ...
- JavaScript Date对象和函数 (一)
JavaScript_Date对象说明 Date中文为"日期"的意思,Date继承自Object对象,此对象提供操作,显示日期与时间的函数 Date对象构造函数 Date对象具有 ...
- 配置IDEA项目JDK环境
打开IDEA,然后点击[Configure]->[Project Defaults]->[Project Structure],如下图: 然后左侧点击树形菜单的[Project Sett ...
- 24V低压检测电路 - 低压检测电压(转)
24V低压检测电路 - 低压检测电压 参考: ADC采样工作原理详解 使用单片机的ADC采集电阻的分压 问题: 当ADC采集两个电阻分压后的电压的时候,ADC转换出来的电压值和万用表量出来的不一样差异 ...
- centos7.5系统elasticsearch使用滚动和全新安装升级到最新的elasticsearch7.4.2版本
背景: 生产环境大量使用 elasticsearch 集群,不同的业务使用不同版本的elasticsearch es经常曝出一些大的漏洞,需要进行版本升级,并且使用x-pack的基本验证功能,避免用户 ...