在上一次【http://www.cnblogs.com/webor2006/p/8419565.html】中演示了多Product多Consumer假死的情况,这次解决假死的情况来实现一个真正的多线程下的生产者消费者模型,在解决之前来看一下wait()和notify()的官方文档,因为假死的原因就跟这两方法有关:

而其中0就代表永远等待,如果给wait中传一个大于0的参数那就是wait指定时间之后就不wait了,好继续往下看wait()的官方注释:

其这句话说到了一个重点:调用wait()方法其实是释放了监听器的所有权,并且当被唤醒之后并非立马就能够执行,而是需要再去获取直接获取成功之后才会执行它下面的代码。而对于wait()过的线程是需要能过notify()或notifyAll()来唤醒的,而notify()是通知一个线程唤醒,而notifyAll()是会将wait()在同一个monitor的所有线程都会唤醒,而解决之前多个生产者与消费者死锁就得用到notifyAll()这个方法了,下面来用它将死锁的程序进行改造,再改之前先贴一下原来有BUG的代码:

public class ProductConsumerVersion2 {
private final Object LOCK = new Object();
private int i = 1;
/* 此标识用来说明是否当前已经生产过了,默认没有 */
private volatile boolean isProduced = false; private void product() {
synchronized (LOCK) {
if (isProduced) {
try {
System.out.println(Thread.currentThread().getName() + " wait了");
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
i++;
System.out.println(Thread.currentThread().getName() + " 生产了-->" + i);
LOCK.notify();
System.out.println(Thread.currentThread().getName() + " notify了");
isProduced = true;
}
}
} private void comsume() {
synchronized (LOCK) {
if (isProduced) {
System.out.println(Thread.currentThread().getName() + " 消费了-->" + i);
LOCK.notify();
System.out.println(Thread.currentThread().getName() + " notify了");
isProduced = false;
} else {
try {
System.out.println(Thread.currentThread().getName() + " wait了");
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} public static void main(String[] args) {
ProductConsumerVersion2 productConsumerVersion2 = new ProductConsumerVersion2(); Stream.of("P1", "P2").forEach(n -> new Thread(n) {
@Override
public void run() {
while (true)
productConsumerVersion2.product();
}
}.start()); Stream.of("C1", "C2").forEach(n -> new Thread(n) {
@Override
public void run() {
while (true)
productConsumerVersion2.comsume();
}
}.start());
} }

好,接下来进行改造:

类似的,对于消费方法也进行相应的修改:

那为什么要用while进行改造呢?如果改用if就不行么,那假设改用if也可以,那咱们来分析下:

如果有两个生产者线程p1、p2,一个消费者线程c1,目前队列中已经有一个数据待c1进行消费,所以此时p1、p2都已经wait()住了,因为得待c1消费完来notifyall();这时c1将队列中的数据消费掉了然后用notifyAll()通知p1、p2进行数据生产,此时p1抢到锁了,于是乎会往下执行数据生产,如下:

而当执行完p1就释放锁了,此时正在wait()的p2抢到锁之后由于是if,所以也开始生产数据了,这样就出现一个尴尬的局面:生产了两个数据,然后才开始消费,而咱们预期的是生产一个消费一个,所以这就是为啥需要用while的原因所在,下面用程序来演示一下这种异常情况:

public class ProductConsumerVersion3 {
private final Object LOCK = new Object();
private int i = 1;
/* 此标识用来说明是否当前已经生产过了,默认没有 */
private volatile boolean isProduced = false; private void product() {
synchronized (LOCK) {
if (isProduced) {
try {
// System.out.println(Thread.currentThread().getName() + " wait了");
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} i++;
System.out.println(Thread.currentThread().getName() + " 生产了-->" + i);
LOCK.notifyAll();
// System.out.println(Thread.currentThread().getName() + " notify了");
isProduced = true;
}
} private void comsume() {
synchronized (LOCK) {
if (!isProduced) {
try {
// System.out.println(Thread.currentThread().getName() + " wait了");
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} System.out.println(Thread.currentThread().getName() + " 消费了-->" + i);
LOCK.notifyAll();
// System.out.println(Thread.currentThread().getName() + " notify了");
isProduced = false;
}
} public static void main(String[] args) {
ProductConsumerVersion3 productConsumerVersion2 = new ProductConsumerVersion3();

     //用三个生产者和二个消费者来模式,貌似生产者与消费者的个数一样难得出我们预期的异常情况
Stream.of("P1", "P2", "P3").forEach(n -> new Thread(n) {
@Override
public void run() {
while (true) {
productConsumerVersion2.product();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start()); Stream.of("C1", "C2").forEach(n -> new Thread(n) {
@Override
public void run() {
while (true) {
productConsumerVersion2.comsume();
try {//为了便于观察打印这里小休眠一会
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start());
} }

编译运行:

解决办法也就是将其if改为while啦,如下:

public class ProductConsumerVersion3 {
private final Object LOCK = new Object();
private int i = 1;
/* 此标识用来说明是否当前已经生产过了,默认没有 */
private volatile boolean isProduced = false; private void product() {
synchronized (LOCK) {
while (isProduced) {
try {
// System.out.println(Thread.currentThread().getName() + " wait了");
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} i++;
System.out.println(Thread.currentThread().getName() + " 生产了-->" + i);
LOCK.notifyAll();
// System.out.println(Thread.currentThread().getName() + " notify了");
isProduced = true;
}
} private void comsume() {
synchronized (LOCK) {
while (!isProduced) {
try {
// System.out.println(Thread.currentThread().getName() + " wait了");
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} System.out.println(Thread.currentThread().getName() + " 消费了-->" + i);
LOCK.notifyAll();
// System.out.println(Thread.currentThread().getName() + " notify了");
isProduced = false;
}
} public static void main(String[] args) {
ProductConsumerVersion3 productConsumerVersion2 = new ProductConsumerVersion3(); Stream.of("P1", "P2", "P3").forEach(n -> new Thread(n) {
@Override
public void run() {
while (true) {
productConsumerVersion2.product();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start()); Stream.of("C1", "C2").forEach(n -> new Thread(n) {
@Override
public void run() {
while (true) {
productConsumerVersion2.comsume();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start());
} }

编译运行:

所以下面对其总结一下:

1、为啥木有死锁了,是因为将notify改为notifyAll了。

2、为啥生产消费木有错乱,是因为使用了while循环来判断是不需要进行wait()。

java线程基础巩固---多线程下的生产者消费者模型,以及详细介绍notifyAll方法的更多相关文章

  1. 多线程学习-基础(十二)生产者消费者模型:wait(),sleep(),notify()实现

    一.多线程模型一:生产者消费者模型   (1)模型图:(从网上找的图,清晰明了) (2)生产者消费者模型原理说明: 这个模型核心是围绕着一个“仓库”的概念,生产者消费者都是围绕着:“仓库”来进行操作, ...

  2. Java多线程14:生产者/消费者模型

    什么是生产者/消费者模型 一种重要的模型,基于等待/通知机制.生产者/消费者模型描述的是有一块缓冲区作为仓库,生产者可将产品放入仓库,消费者可以从仓库中取出产品,生产者/消费者模型关注的是以下几个点: ...

  3. Java多线程-并发协作(生产者消费者模型)

    对于多线程程序来说,不管任何编程语言,生产者和消费者模型都是最经典的.就像学习每一门编程语言一样,Hello World!都是最经典的例子. 实际上,准确说应该是“生产者-消费者-仓储”模型,离开了仓 ...

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

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

  5. 线程高级篇-Lock锁实现生产者-消费者模型

    Lock锁介绍: 在java中可以使用 synchronized 来实现多线程下对象的同步访问,为了获得更加灵活使用场景.高效的性能,java还提供了Lock接口及其实现类ReentrantLock和 ...

  6. 进击的Python【第九章】:paramiko模块、线程与进程、各种线程锁、queue队列、生产者消费者模型

    一.paramiko模块 他是什么东西? paramiko模块是用python语言写的一个模块,遵循SSH2协议,支持以加密和认证的方式,进行远程服务器的连接. 先来个实例: import param ...

  7. 基于线程实现的生产者消费者模型(Object.wait(),Object.notify()方法)

    需求背景 利用线程来模拟生产者和消费者模型 系统建模 这个系统涉及到三个角色,生产者,消费者,任务队列,三个角色之间的关系非常简单,生产者和消费者拥有一个任务队列的引用,生产者负责往队列中放置对象(i ...

  8. 转: 【Java并发编程】之十三:生产者—消费者模型(含代码)

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17249321 生产者消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一 ...

  9. 【Java并发编程】之十三:生产者—消费者模型

    生产者消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一存储空间,生产者向空间里生产数据,而消费者取走数据. ​ 这里实现如下情况的生产--消费模型: ​ 生产者不断交替地生产两组 ...

随机推荐

  1. 纹理特征描述之自相关函数法 纹理粗糙性与自相关函数的扩展成正比 matlab代码实现

    图像中通常采用自相关函数作为纹理测度 自相关函数的定义为: ​ 调用自定义函数 zxcor()对砖墙面和大理石面纹理进行分析: 自定义函数 zxcor(): function [epsilon,eta ...

  2. import、from 模块 import*、reload

    import 模块名.from 模块名 import* 均为导入模块,前者调用模块中函数或者变量时需要添加引用,即模块名.调用函数或者变量名 具体用法见下 https://jingyan.baidu. ...

  3. USACO 1.2 Friday the Thirteenth

    注意闰月的部分细节很多. /* ID:Starry21 LANG:C++ TASK:friday */ #include<iostream> #include<string> ...

  4. 动态加载js不执行解决办法

    这个问题的产生原因是:我们项目有一个主index文件,在主index文件中需要根据参数来判断是加载pc.html的内容还是加载mobile.html的内容,一开始是使用jquery来做的,没有问题,后 ...

  5. 修改Jupyter Notebook默认目录

    Jupyter Notebook每次打开都需要先进到相应的文件夹再打开 很不方便 首先进入到Jupyter的安装目录,我的是 D:\Anaconda3\Scripts 然后,输入命令: jupyter ...

  6. python列表的切片与复制

    切片,即处理一个完整列表中部分数据. 语法 变量[起始索引:终止索引:步长] 首先创建一个字符串列表 >>> cars = ['toyota', 'honda', 'mazda', ...

  7. 【一个蒟蒻的挣扎】最小生成树—Kruskal算法

    济南集训第五天的东西,这篇可能有点讲不明白提前抱歉(我把笔记忘到别的地方了 最小生成树 概念:一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的 ...

  8. pyton 类(4) 静态方法

    class Cat(): tag = ' 猫科动物 ' def __init__(self, name): self.name = name @staticmethod def breah(): pr ...

  9. spark教程(18)-sparkSQL 自定义函数

    sparkSQL 也允许用户自定义函数,包括 UDF.UDAF,但没有 UDTF 官方 API class pyspark.sql.UDFRegistration(sparkSession)[sour ...

  10. X86逆向14:常见的脱壳手法

    本章节内容将介绍软件的脱壳技术.什么是加壳?加壳就是用来压缩或者保护软件不被非法修改破解的一种工具,而脱壳就是将已经加壳的程序从壳中剥离出来,既然能给程序进行加壳,那也就会有相应的脱壳方法,本节课我们 ...