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 ...
随机推荐
- Xamarin.Forms 自定义控件(呈现器和效果)
Xamarin.Forms 使用目标平台的本机控件呈现用户界面,从而让 Xamarin.Forms 应用程序为每个平台保留了相应的界面外观.凭借效果,无需进行自定义呈现器实现,即可自定义每个平台上的本 ...
- 洛谷 P1972 [SDOI2009]HH的项链-二维偏序+树状数组+读入挂(离线处理,思维,直接1~n一边插入一边查询),hahahahahahaha~
P1972 [SDOI2009]HH的项链 题目背景 无 题目描述 HH 有一串由各种漂亮的贝壳组成的项链.HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含 ...
- js字符串连接
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> </head> ...
- JS 根据不同年份,获取不同月份的天数
在做日期方面的需求时,需要考虑到大小月的问题 ##格式:new Date(year,month,0).getDate() new Date(2019,12,0).getDate() 可以在控制台打印 ...
- iptables只允许指定ip访问本机的指定端口
首先,清除所有预设置 iptables -F 其次,设置只允许指定ip地址访问指定端口 1.在tcp协议中,禁止所有的ip访问本机的1521端口. iptables -I INPUT -p tcp - ...
- java集合代码示例
一.List ArrayList 使用List时,最好初始化容量. ArrayList的默认容量为10,每次扩容增0.5倍,假如要放置100个元素,需要多次扩容. List<String> ...
- C++的static_cast、dynamic_cast和const_cast用法
static_cast.dynamic_cast和const_cast static_cast: 用法: static_cast < type-id > (expression) ...
- Net core学习系列(十)——Net Core配置
一.前言 选项(Options)模式是对配置(Configuration)的功能的延伸.在12章(ASP.NET Core中的配置二)Configuration中有介绍过该功能(绑定到实体类.绑定至对 ...
- Mac OSX下增加TCP连接数
查看 max files: $ sysctl -a | grep files kern.maxfiles = 12288 kern.maxfilesperproc = 10240 修改: $ sudo ...
- android : 解决android无法使用sun.misc.BASE64Encoder sun.misc.BASE64Decoder 的问题, 无需添加rt.jar
一共包含: BASE64Decoder.java BASE64Encoder.java CEFormatException.java CEStreamExhausted.java CharacterD ...