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

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

店员:

/**
* 店员
*/
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. mysql 日期 字符串 时间戳转换

    #时间转字符串 select date_format(now(), '%Y-%m-%d'); -02-27 #时间转时间戳 select unix_timestamp(now()); #字符串转时间 ...

  2. JavaScript获取事件对象和目标对象

    在JavaScript开发中,经常需要获取触发某个事件的目标对象.让后根据目标对象进行不同的业务处理.下面展示通过JavaScript获取触发事件的事件目标对象.如下: Js代码 1 2 3 4 5 ...

  3. 提高你的Java代码质量吧:谨慎包装类型的比较

    一.分析  基本类型可以比较大小,其所对应的包装类型都实现了Comparable接口此问题. 二.场景  代码如下: public class Client{ public static void m ...

  4. 在Windows Azure上创建ASP.NET MVC网站

    本篇体验在Windows Azure上创建ASP.NET MVC网站. →登录到Windows Azure管理门户 →点击左下方的"新建" →点击"自定义创建" ...

  5. 【pycharm】pycharm上安装tensorflow,报错:AttributeError: module 'pip' has no attribute 'main' 解决方法

    pycharm上安装tensorflow,报错:AttributeError: module 'pip' has no attribute 'main' 解决方法 解决方法: 在pycharm的安装目 ...

  6. DotNetty 学习

    [转载]http://www.cnblogs.com/littlegod/p/7699482.html DotNetty的学习是带着如下这些问题展开: 1. Socket基础框架方案: 通信模式:异步 ...

  7. JForum 源码分析

    怎么才算好的源码分析呢?当然我这个肯定不算.我想大概分为几个层面吧,写写注释那算最基本的了,写写要点思路和难点,算是还不错拉,再难的就是跳出源码举一反三,形成自己的一套思路吧.好好努力吧. 这次针对的 ...

  8. Swift - 用UIScrollView实现视差动画效果

    Swift - 用UIScrollView实现视差动画效果 效果 源码 https://github.com/YouXianMing/Swift-Animations // // MoreInfoVi ...

  9. 升级IOS8游戏上传自定义头像功能失效的问题

    为了支持arm64,之前已经折腾了很久,昨晚打包准备提交苹果审核时,测试那边的同事反馈说游戏上传自定义头像功能不可用了. 游戏上传自定义功能的简介:卡牌游戏最初是<比武招亲>中有一个充VI ...

  10. Wireshark的简介

    -------------------------------------------------------------- <Wireshark数据包分析实战>这本书其实还很不错,当时买 ...