模拟通过线程实现消费者和订阅者模式:

首先,定义一个店员:店员包含进货、卖货方法;其次,定义一个生产者,生产者负责给店员生产产品;再者,定义一个消费者,消费者负责从店员那里消费产品。

店员:

/**
* 店员
*/
class Clerk {
private int product = 0; /**
* 进货
*/
public synchronized void purchase() {
if (product >= 10) {
System.out.println("产品已满。。。");
} else {
System.out.println(Thread.currentThread().getName() + ":" + ++product);
}
} /**
* 卖货
*/
public synchronized void sell() {
if (product <= 0) {
System.out.println("产品缺貨。。。");
} else {
System.out.println(Thread.currentThread().getName() + ":" + --product);
}
}
}

生产者

/**
* 生产者 不断的生产产品给店员
* */
class Productor implements Runnable{
private Clerk clerk;
public Productor(Clerk clerk){
this.clerk=clerk;
} public void run() {
for(int i=0;i<20;i++){
clerk.purchase();
}
}
}

消费者

/**
* 消费者 不断的从店员那里消费产品
* */
class Consumer implements Runnable{
private Clerk clerk;
public Consumer(Clerk clerk){
this.clerk=clerk;
} public void run() {
for(int i=0;i<20;i++){
clerk.sell();
}
}
}

此时,运行程序,运行结果如下:

Productor-A:1
Productor-A:2
Productor-A:3
Productor-A:4
Productor-A:5
Productor-A:6
Productor-A:7
Productor-A:8
Productor-A:9
Productor-A:10
产品已满。。。
产品已满。。。
产品已满。。。
产品已满。。。
产品已满。。。
产品已满。。。
产品已满。。。
产品已满。。。
产品已满。。。
产品已满。。。
Consumer-A:9
Consumer-A:8
Consumer-A:7
Consumer-A:6
Consumer-A:5
Consumer-A:4
Consumer-A:3
Consumer-A:2
Consumer-A:1
Consumer-A:0
产品缺貨。。。
产品缺貨。。。
产品缺貨。。。
产品缺貨。。。
产品缺貨。。。
产品缺貨。。。
产品缺貨。。。
产品缺貨。。。
产品缺貨。。。
产品缺貨。。。

从运行打印结果可以发现这里存在两个问题:

1)一旦生产者发现店员产品已满时,仍然没有停止生产产品,在不断地生产生产产品;

2)一旦消费者发现店员产品缺货时,依然时不断地消费消费。

这里明显是有缺陷的,现实中应该是:一旦发现货物满时,就不在进货,而是开启卖货行为;当卖货行为发现无货时,开始进货行为。

针对生产者消费者改进:

消费者、生产者、客户端调用代码不变,只修改店员类:

/**
* 店员
*/
class Clerk {
private int product = 0; /**
* 进货
*/
public synchronized void purchase() {
if (product >= 10) {
System.out.println("产品已满。。。");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println(Thread.currentThread().getName() + ":" + ++product);
this.notifyAll();
}
} /**
* 卖货
*/
public synchronized void sell() {
if (product <= 0) {
System.out.println("产品缺貨。。。");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println(Thread.currentThread().getName() + ":" + --product);
this.notifyAll();
}
}
}

此时运行结果:

Productor-A:1
Consumer-A:0
产品缺貨。。。
Productor-A:1
Productor-A:2
Productor-A:3
Productor-A:4
Productor-A:5
Productor-A:6
Productor-A:7
Productor-A:8
Productor-A:9
Productor-A:10
产品已满。。。
Consumer-A:9
Consumer-A:8
Consumer-A:7
Consumer-A:6
Consumer-A:5
Consumer-A:4
Consumer-A:3
Consumer-A:2
Consumer-A:1
Consumer-A:0
产品缺貨。。。
Productor-A:1
Productor-A:2
Productor-A:3
Productor-A:4
Productor-A:5
Productor-A:6
Productor-A:7
Productor-A:8
Consumer-A:7
Consumer-A:6
Consumer-A:5
Consumer-A:4
Consumer-A:3
Consumer-A:2
Consumer-A:1

此时,从结果运行来说是按照我们希望的结果出现了。

改进后带来问题一:

修改店员类的最大进货数为1,把生产者一次生产20修改为2,消费者一次消费20也修改为2。

/**
* 生产者 不断的生产产品给店员
*/
class Productor implements Runnable {
private Clerk clerk; public Productor(Clerk clerk) {
this.clerk = clerk;
} public void run() {
for (int i = 0; i < 2; i++) {
clerk.purchase();
}
}
} /**
* 消费者 不断的从店员那里消费产品
*/
class Consumer implements Runnable {
private Clerk clerk; public Consumer(Clerk clerk) {
this.clerk = clerk;
} public void run() {
for (int i = 0; i < 2; i++) {
clerk.sell();
}
}
}

店员类:

/**
* 店员
*/
class Clerk {
private int product = 0; /**
* 进货
*/
public synchronized void purchase() {
if (product >= 1) {
System.out.println("产品已满。。。");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println(Thread.currentThread().getName() + ":" + ++product);
this.notifyAll();
}
} /**
* 卖货
*/
public synchronized void sell() {
if (product <= 0) {
System.out.println("产品缺貨。。。");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println(Thread.currentThread().getName() + ":" + --product);
this.notifyAll();
}
}
}

此时运行结果:

从运行结果上来看,程序是一个死锁现象。

为什么会发生死锁问题?

从运行结果上来看分析:

分析:

1)当purchase()运行“2”是处于this.wait()等待状态时,此时sell()开始运行;

2)sell()运行时,第一次走3当运行到this.notifyAll()时,开始运行4和purchase()等待向下执行(一旦向下执行purchase将不再被调用,原因生产者只有两次循环机会),而运行‘4’时打印‘产品缺货’,而且代码走入this.wait()处于一直等待状态。

因此会看到程序一直未结束状态,这个属于代码的一个BUG。

解决方法:

修改店员类:

/**
* 店员
*/
class Clerk {
private int product = 0; /**
* 进货
*/
public synchronized void purchase() {
if (product >= 1) {
System.out.println("产品已满。。。");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} System.out.println(Thread.currentThread().getName() + ":" + ++product);
this.notifyAll();
} /**
* 卖货
*/
public synchronized void sell() {
if (product <= 0) {
System.out.println("产品缺貨。。。");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} System.out.println(Thread.currentThread().getName() + ":" + --product);
this.notifyAll();
}
}

在运行发现程序正常结束,打印结果如下:

Productor-A:1
产品已满。。。
Consumer-A:0
产品缺貨。。。
Productor-A:1
Consumer-A:0

改进后带来问题二(虚假唤醒):

此时修改客户端调用代码:

public class SpuriousWakeupsTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor productor = new Productor(clerk);
Consumer consumer = new Consumer(clerk); new Thread(productor, "Productor-A").start();
new Thread(consumer, "Consumer-A").start(); new Thread(productor, "Productor-B").start();
new Thread(consumer, "Consumer-B").start();
}
}

之前只有一个生产者和一个消费者,修改后让其拥有两个生产者和两个消费者,此时运行代码如下:

Consumer-A:-1
产品缺貨。。。
Productor-A:0
Productor-A:1
Consumer-A:0
Consumer-B:-1
产品缺貨。。。
Productor-B:0
Productor-B:1
Consumer-B:0

从代码分析逻辑来看,输出解雇貌似毫无逻辑,此现象就是一个虚假唤醒现象。

问题分析:

1)缺货时,两个consumer线程都进入wait状态;

2)当另外一个生产者生产了产品并调用了notifyall,此时两个consumer线程都被唤醒并跳过wait,进入消费代码

        System.out.println(Thread.currentThread().getName() + ":" + --product);

,因此导致一个输出产品数为0,另外一个产品数为-1。

解决办法:

基于while来反复判断进入正常操作的临界条件是否满足:

    synchronized (obj) {
while (<condition does not hold>)
obj.wait();
... // Perform action appropriate to condition
}

此处理方案来此ava.lang.Object API的wati方法说明信息中。

店员修改:

/**
* 店员
*/
class Clerk {
private int product = 0; /**
* 进货
*/
public synchronized void purchase() {
while (product >= 1) {
System.out.println("产品已满。。。");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} System.out.println(Thread.currentThread().getName() + ":" + ++product);
this.notifyAll();
} /**
* 卖货
*/
public synchronized void sell() {
while (product <= 0) {
System.out.println("产品缺貨。。。");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} System.out.println(Thread.currentThread().getName() + ":" + --product);
this.notifyAll();
}
}

运行结果:

Productor-A:1
产品已满。。。
Consumer-B:0
产品缺貨。。。
Productor-B:1
产品已满。。。
Consumer-A:0
产品缺貨。。。
Productor-B:1
Consumer-B:0
Productor-A:1
Consumer-A:0

Java-JUC(八):使用wait,notify|notifyAll完成生产者消费者通信,虚假唤醒(Spurious Wakeups)问题出现场景,及问题解决方案。的更多相关文章

  1. java多线程 生产者消费者案例-虚假唤醒

    package com.java.juc; public class TestProductAndConsumer { public static void main(String[] args) { ...

  2. java 多线程之wait(),notify,notifyAll(),yield()

    wait(),notify(),notifyAll()不属于Thread类,而是属于Object基础类,也就是说每个对像都有wait(),notify(),notifyAll()的功能.因为都个对像都 ...

  3. Java多线程之wait(),notify(),notifyAll()

    在多线程的情况下,因为同一进程的多个线程共享同一片存储空间,在带来方便的同一时候,也带来了訪问冲突这个严重的问题.Java语言提供了专门机制以解决这样的冲突,有效避免了同一个数据对象被多个线程同一时候 ...

  4. 如何在 Java 中正确使用 wait, notify 和 notifyAll – 以生产者消费者模型为例

    wait, notify 和 notifyAll,这些在多线程中被经常用到的保留关键字,在实际开发的时候很多时候却并没有被大家重视.本文对这些关键字的使用进行了描述. 在 Java 中可以用 wait ...

  5. 转:【Java并发编程】之十:使用wait/notify/notifyAll实现线程间通信的几点重要说明

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17225469    在Java中,可以通过配合调用Object对象的wait()方法和no ...

  6. 【java线程系列】java线程系列之线程间的交互wait()/notify()/notifyAll()及生产者与消费者模型

    关于线程,博主写过java线程详解基本上把java线程的基础知识都讲解到位了,但是那还远远不够,多线程的存在就是为了让多个线程去协作来完成某一具体任务,比如生产者与消费者模型,因此了解线程间的协作是非 ...

  7. 【Java并发编程】:使用wait/notify/notifyAll实现线程间通信

    在java中,可以通过配合调用Object对象的wait()方法和notify()方法或notifyAll()方法来实现线程间的通信.在线程中调用wait()方法,将阻塞等待其他线程的通知(其他线程调 ...

  8. 【Java并发编程】之十:使用wait/notify/notifyAll实现线程间通信的几点重要说明

    在Java中,可以通过配合调用Object对象的wait()方法和notify()方法或notifyAll()方法来实现线程间的通信.在线程中调用wait()方法,将阻塞等待其他线程的通知(其他线程调 ...

  9. java并发编程(十)使用wait/notify/notifyAll实现线程间通信

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17225469 wait()方法:public final void wait()  thr ...

随机推荐

  1. 马士兵hadoop第二课:hdfs集群集中管理和hadoop文件操作

    马士兵hadoop第一课:虚拟机搭建和安装hadoop及启动 马士兵hadoop第二课:hdfs集群集中管理和hadoop文件操作 马士兵hadoop第三课:java开发hdfs 马士兵hadoop第 ...

  2. linux_远程copy

    1:远程copy [linux对linux 远程拷贝]   scp 文件名 root@远程ip:/路径/   将本地home目录下的test.tar的文件拷贝到远程主机192.168.1.23的/ho ...

  3. Ubuntu下实现软路由(转)

    参考:http://www.openwrt.pro/post-292.html 个人看法: 1.实现路由在Linux下必须要用到iptables进行转发,这才是路由核心. 2.我觉得对于Linux来说 ...

  4. What is OpenOCD?

    About OpenOCD was created by Dominic Rath as part of a 2005 diploma thesis written at the University ...

  5. dotNetSpider 手记

    准备工作: 从github上download工程. 安装VS2017. 安装 .net core 2.0. 编译通过. 基础架构: 调度器 Scheduler 从根site开始,向 Downloade ...

  6. android开发之GestureDetector手势识别(调节音量、亮度、快进和后退)

    写UI布局: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:andro ...

  7. [web.config]如何灵活使用配置文件

    摘要 在实际项目中,经常遇到比较多的环境,比如开发环境,测试环境,生产环境.对于这些环境,可能会有不同接口调用,不同的数据库连接字符串等等.那么该如何实现不同环境的参数快速切换呢?当然,最笨的方式就是 ...

  8. Android项目更换开发环境时出现的 java.lang.VerifyError 异常解决办法

    from://http://blog.csdn.net/wudiwo/article/details/7548451 项目是从同事的电脑上直接拷贝过来的,项目里面的jar包是在项目跟下libs里面存放 ...

  9. webrtc fec

    转自:http://www.cnblogs.com/webrtc/p/7402570.html WebRTC::FEC [TOC] Tags: WebRTC FEC WebRTC中的 FEC 实现分为 ...

  10. Java 8新的时间日期库的20个使用示例

    原文链接 作者:Javin Paul 译者:之诸暇 除了lambda表达式,stream以及几个小的改进之外,Java 8还引入了一套全新的时间日期API,在本篇教程中我们将通过几个简单的任务示例来学 ...