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

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

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

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

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

  • 库存类:

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. EasyExcel引入

    好久没更了,都在有道云上面记录,没时间搬过来. easyexcel是最近项目做优化涉及的一个改善点吧. 简介 导出是后台管理系统的常用功能,当数据量特别大的时候会内存溢出和卡顿页面,曾经自己封装过一个 ...

  2. 使用google autoservice 自动生成java spi 描述文件

    spi 是一种服务发现的标准,对于开发中我们通常需要编写 META-INF/services 文件夹中定义的类. google auto 中的autoservice 可以帮助我们生成对应的配置,很方便 ...

  3. 判断一个数是否能整开方,perfect square

    int m,n;m=sqrt(n);m*m==n?yes:no; https://www.codewars.com/kata/56269eb78ad2e4ced1000013/solutions/ja ...

  4. C++后端工程师需要看的书籍

    C++基础书籍<C++ primer><深度探索C++对象模型><Effective C++><more effective C++><STL源码 ...

  5. CCF 201909-5 城市规划

    试题编号: 201909-5 试题名称: 城市规划 时间限制: 3.0s 内存限制: 512.0MB 问题描述: 几乎是Gym102222G的原版,详解见上一篇博文 /* 贡献+树形dp+01背包 * ...

  6. 分类模型的评价指标Fscore

    小书匠深度学习 分类方法常用的评估模型好坏的方法. 0.预设问题 假设我现在有一个二分类任务,是分析100封邮件是否是垃圾邮件,其中不是垃圾邮件有65封,是垃圾邮件有35封.模型最终给邮件的结论只有两 ...

  7. 分析WordPress数据表之评论表(功能篇)

    数据表分析 wp_comments(评论表) 该表字段,如下:comment_ID(评论ID)comment_post_ID(评论文章ID)comment_author(评论者用户名)comment_ ...

  8. [技术博客] 如何避免在代码中多重render

    目录 问题发现 方案1 extracted_method and return(父函数and return法) 方案2 子函数yield,父函数调用后{return} 方案3 extracted_me ...

  9. [Web] How to Test React and MobX with Jest

    转载自: https://semaphoreci.com/community/tutorials/how-to-test-react-and-mobx-with-jest?utm_content=bu ...

  10. bad ELF interpreter: No such file or directory

    1.在64系统里执行32位程序如果出现/lib/ld-linux.so.2: bad ELF interpreter: No such file or directory,安装下glic即可 yum ...