以下是一个案例,有一个店员,负责进货和卖货。进货生产,卖货消费。 当商品超过10件,生产等待,消费继续,当少于0件,消费等待,消费继续。
package com.gf.demo;
/**
* 生产者消费者案例,演示虚假唤醒问题
*/
public class TestProductorAndConsumer {
public static void main(String args[]){
Clerk clerk = new Clerk();
Productor pro = new Productor(clerk);
Consumer cos = new Consumer(clerk);
new Thread(pro , "生产者A").start();
new Thread(cos , "消费者B").start();
}
}
/**
* 店员
*/
class Clerk {
private int product = 0;
/**
* 进货
*/
public synchronized void get() {
if (5 <= product) {
System.out.println("产品已满 !");
} else {
System.out.println(Thread.currentThread().getName() + " : " + ++product);
}
}
/**
* 卖货
*/
public synchronized void sale() {
if (0 >= product) {
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;
}
@Override
public void run() {
for (int i = 0 ; i < 10 ; i++) {
clerk.get();
}
}
}
/**
* 消费者
*/
class Consumer implements Runnable {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0 ; i<10 ; i++) {
clerk.sale();
}
}
}
我们不加等待唤醒机制 ,演示结果如下,生产和消费过快,不等待彼此。
我们加入等待唤醒机制:
/**
* 店员
*/
class Clerk {
private int product = 0;
/**
* 进货
*/
public synchronized void get() {
if (5 <= product) {
System.out.println("产品已满 !");
try {
//等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println(Thread.currentThread().getName() + " : " + ++product);
//唤醒
this.notifyAll();
}
}
/**
* 卖货
*/
public synchronized void sale() {
if (0 >= product) {
System.out.println("缺货 !");
try {
//等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println(Thread.currentThread().getName() + " : " + --product);
//唤醒
this.notifyAll();
}
}
}
结果:
很和谐 “没问题”。
但如果我们让 生产慢于消费 如下:
/**
* 店员
*/
class Clerk {
private int product = 0;
/**
* 进货
*/
public synchronized void get() {
if (1 <= product) {
System.out.println("产品已满 !");
try {
//等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println(Thread.currentThread().getName() + " : " + ++product);
//唤醒
this.notifyAll();
}
}
/**
* 卖货
*/
public synchronized void sale() {
if (0 >= product) {
System.out.println("缺货 !");
try {
//等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println(Thread.currentThread().getName() + " : " + --product);
//唤醒
this.notifyAll();
}
}
}
/**
* 生成者
*/
class Productor implements Runnable {
private Clerk clerk;
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0 ; i <10 ; i++) {
try {
//休息200毫秒,让生产慢于消费
Thread.sleep( 200 );
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.get();
}
}
}
/**
* 消费者
*/
class Consumer implements Runnable {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0 ; i<10 ; i++) {
clerk.sale();
}
}
}
结果:
程序最后没有结束,那么说明我们写还是有问题。
原因:这是因为生成者速度较慢,消费最后一次wait()被 notify 唤醒后,不会在执行else中的方法,那么 product数量没有变成0,导致生成者中的进入wait() ,之后就再也没有人唤醒他,这样程序就直接结束不了。
解决方法:掉 else
/**
* 店员
*/
class Clerk {
private int product = 0;
/**
* 进货
*/
public synchronized void get() {
if (1 <= product) {
System.out.println( "产品已满 !" );
try {
//等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println( Thread.currentThread().getName() + " : " + ++product );
//唤醒
this.notifyAll();
}
/**
* 卖货
*/
public synchronized void sale() {
if (0 >= product) {
System.out.println( "缺货 !" );
try {
//等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println( Thread.currentThread().getName() + " : " + --product );
//唤醒
this.notifyAll();
}
}
上面的案例是单个生产者单个消费者,若我们使用两个生产者,两个消费者,如下:
package com.gf.demo;
/**
* 生产者消费者案例,演示虚假唤醒问题
*/
public class TestProductorAndConsumer {
public static void main(String args[]) {
Clerk clerk = new Clerk();
Productor pro = new Productor( clerk );
Consumer cos = new Consumer( clerk );
new Thread( pro, "生产者A" ).start();
new Thread( cos, "消费者B" ).start();
new Thread( pro, "生产者C" ).start();
new Thread( cos, "消费者D" ).start();
}
}
/**
* 店员
*/
class Clerk {
private int product = 0;
/**
* 进货
*/
public synchronized void get() {
if (1 <= product) {
System.out.println( "产品已满 !" );
try {
//等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println( Thread.currentThread().getName() + " : " + ++product );
//唤醒
this.notifyAll();
}
/**
* 卖货
*/
public synchronized void sale() {
if (0 >= product) {
System.out.println( "缺货 !" );
try {
//等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println( Thread.currentThread().getName() + " : " + --product );
//唤醒
this.notifyAll();
}
}
/**
* 生成者
*/
class Productor implements Runnable {
private Clerk clerk;
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
//休息200毫秒,让生产慢于消费
Thread.sleep( 200 );
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.get();
}
}
}
/**
* 消费者
*/
class Consumer implements Runnable {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
clerk.sale();
}
}
}
运行结果:
出现了负数,代码肯定有问题。
这种现象叫做虚假唤醒,jdk文档写的很清楚,如下:
所以我们按照文档告诉我们,把 if 改为while就可以解决问题 ,循环时,在判断一次。
/**
* 店员
*/
class Clerk {
private int product = 0;
/**
* 进货
*/
public synchronized void get() {
while (1 <= product) {
System.out.println( "产品已满 !" );
try {
//等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println( Thread.currentThread().getName() + " : " + ++product );
//唤醒
this.notifyAll();
}
/**
* 卖货
*/
public synchronized void sale() {
while (0 >= product) {
System.out.println( "缺货 !" );
try {
//等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println( Thread.currentThread().getName() + " : " + --product );
//唤醒
this.notifyAll();
}
}
运行结果:
问题解决了 !
评论