我们将生产者、消费者、库存、和调用线程的主函数分别写进四个类中,通过抢夺非线程安全的数据集合来直观的表达在进行生产消费者模型的过程中可能出现的问题与解决办法。

我们假设有一个生产者,两个消费者来共同抢夺库存里的资源,而生产者和消费者都以线程来实现。

库存对象只有是唯一的才会出现抢夺一个资源的可能,所以为了使库存对象是唯一的,我们可以使用两种方法实现,单例模式和通过生产者和消费者的构造函数参数来初始化。

本次举例使用的是构造函数的方法,但代码中也注释出了单例模式的写法与使用。

先创建一个简单的生产消费者模型,查看它的运行结果。

  • 库存类:

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生产消费者模型——代码解析的更多相关文章

  1. JAVA实现生产消费者模型

    前言 最近面试比较多,发现生产消费者模型在各公司面试的过程中问的还是比较多的,记录一下常见JAVA实现生产者消费模型的代码 思路 我们通过三种模式来实现 通过wait和notify 通过Lock和Co ...

  2. Linux——多线程下解决生产消费者模型

    我们学习了操作系统,想必对生产消费者问题都不陌生.作为同步互斥问题的一个经典案例,生产消费者模型其实是解决实际问题的基础模型,解决很多的实际问题都会依赖于它.而此模型要解决最大的问题便是同步与互斥.而 ...

  3. Python进阶----进程之间通信(互斥锁,队列(参数:timeout和block),), ***生产消费者模型

    Python进阶----进程之间通信(互斥锁,队列(参数:timeout和block),), ***生产消费者模型 一丶互斥锁 含义: ​ ​ ​ 每个对象都对应于一个可称为" 互斥锁&qu ...

  4. Python之queue模块以及生产消费者模型

    队列 队列类似于一条管道,元素先进先出,进put(arg),取get() 有一点需要注意的是:队列都是在内存中操作,进程退出,队列清空,另外,队列也是一个阻塞的形态. 队列分类 队列有很多中,但都依赖 ...

  5. Python并发编程04 /多线程、生产消费者模型、线程进程对比、线程的方法、线程join、守护线程、线程互斥锁

    Python并发编程04 /多线程.生产消费者模型.线程进程对比.线程的方法.线程join.守护线程.线程互斥锁 目录 Python并发编程04 /多线程.生产消费者模型.线程进程对比.线程的方法.线 ...

  6. Python - Asyncio模块实现的生产消费者模型

    [原创]转载请注明作者Johnthegreat和本文链接 在设计模式中,生产消费者模型占有非常重要的地位,这个模型在现实世界中也有很多有意思的对应场景,比如做包子的人和吃包子的人,当两者速度不匹配时, ...

  7. Java生产者消费者模型

    在Java中线程同步的经典案例,不同线程对同一个对象同时进行多线程操作,为了保持线程安全,数据结果要是我们期望的结果. 生产者-消费者模型可以很好的解释这个现象:对于公共数据data,初始值为0,多个 ...

  8. java生产者消费者问题代码分析

    作者要的是一个生产者生成,接着必须有一个消费者消费,那这不是需要单线程吗?或者使用1个大小的阻塞队列.所以只谈论问题本身,不谈论好不好. 具体代码: import java.util.concurre ...

  9. Python——Queue模块以及生产消费者模型

    1.了解Queue Queue是python标准库中的线程安全的队列(FIFO)实现,提供了一个适用于多线程编程的先进先出的数据结构,即队列,用来在生产者和消费者线程之间的信息传递 |queue.Qu ...

随机推荐

  1. python操作excel获取内容

    背景:从excel表中获取请求url.请求数据.请求类型.预期结果 因此,需要学会如何使用python从excel获取这些信息 #coding=utf-8 import xlrd #创建对象时,获取对 ...

  2. Pytorch卷积神经网络识别手写数字集

    卷积神经网络目前被广泛地用在图片识别上, 已经有层出不穷的应用, 如果你对卷积神经网络充满好奇心,这里为你带来pytorch实现cnn一些入门的教程代码 #首先导入包 import torchfrom ...

  3. Content-type解析

    一.是什么? 是Http的实体首部字段,用于说明请求或返回的消息主体是用何种方式编码,在request header和response header里都存在. Content-Type(内容类型),一 ...

  4. Gradle系列教程之依赖管理

    这一章我将介绍Gradle对依赖管理的强大支持,学习依赖分组和定位不同类型仓库.依赖管理看起来很容易,但是当出现依赖解析冲突时就会很棘手,复杂的依赖关系可能导致构建中依赖一个库的多个版本.Gradle ...

  5. java实现开根号的运算

    面试的时候,偶然被问到,开根号的实现,虽然给面试官讲解了思路,但是没有实际实现过,今天闲来无事,就把自己的思路写一下,做个笔记. 如果某个数字正好可以开根号为2个整数,例如1,4,9等,那就很简单了. ...

  6. 【技术博客】Git Flow模型管理代码版本

    参考GIT版本管理:Git Flow模型,在此基础上加入了自己的理解,增加人员分工和相应代码,并根据本次项目的实际情况进行相应修改. 在本学期的软件工程开发过程中,我们从alpha阶段就使用了git ...

  7. mysql中的正则操作 匹配手机号,匹配中文,替换

    mysql中的正则操作 匹配手机号,匹配中文,替换 正则匹配hy_user表内tel字段的电话号码: SELECT * FROM hy_user WHERE tel REGEXP "[1][ ...

  8. BaiduPCS-Go的安装及使用

    BaiduPCS-Go的安装及使用 linux下会提示输入验证码,浏览器打开验证码url,多输入几次 Contents [hide] 一. 软件下载及安装 二. 软件的使用 1. 账号登录与退出 2. ...

  9. postgresql cstore_fdw安装与性能测试

    据介绍,cstore_fdw实现了 PostgreSQL 数据库的列式存储.列存储非常适合用于数据分析的场景,数据分析的场景下数据是批量加载的.这个扩展使用了Optimized Row Columna ...

  10. 005_Philippines之行准备

    一.去前必备的手续 (1)SSP SSP(Special Study Permit),又叫特殊学生许可,是由菲律宾移民局要求的,每一位前往菲律宾进行游学的学生都要办理的一种特殊许可,学校会统一进行办理 ...