一、虚假唤醒示例
public class TestProducerAndCustomer {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
Producer producer2 = new Producer(clerk);
Consumer consumer2 = new Consumer(clerk);
new Thread(producer, "生产者A").start();
new Thread(consumer, "消费者B").start();
new Thread(producer2, "生产者C").start();
new Thread(consumer2, "消费者D").start();
}
}
// 店员类:负责进货和售货
class Clerk {
private int num = 0; //店里当前的货物量
public synchronized void get() { //店员进货 每次进货一个(生产者)
if (num >= 1) {
System.out.println(Thread.currentThread().getName() + " 库存已满,无法进货");
try {
this.wait();
System.out.println(Thread.currentThread().getName() + " wait后剩余步骤");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " : " + (++num));
this.notifyAll();
}
public synchronized void sale() { //店员卖货 每次卖掉一个货(消费者)
if (num <= 0) {
System.out.println(Thread.currentThread().getName() + " 库存已空,无法卖货");
try {
this.wait();
System.out.println(Thread.currentThread().getName() + " wait后剩余步骤");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " : " + (--num));
this.notifyAll();
}
}
// 生产者 可以有很多生产者卖货给这个店员
class Producer implements Runnable {
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; 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 < 20; i++) {
clerk.sale();
}
}
}
运行结果如下:
现在我们来分析一下这个过程
TIME | 发生操作 | 仓库数量 |
---|---|---|
TIME1 | 生产者A拿到锁,进行生产 | 1 |
TIME2 | 生产者A再次拿到锁,因为仓库已满,A等待 | 1 |
TIME3 | 生产者C拿到锁,仓库已满,C也进入等待 | 1 |
TIME4 | 消费者D拿到锁,进行消费,同时唤醒等待中的队列(即下一轮锁资源,将由A、B、C、D一起抢占) | 0 |
TIME5 | 消费者D拿到锁,仓库为空,D等待;A、B、C抢占锁资源 | 0 |
TIME6 | 消费者B拿到锁,仓库为空,B等待 | 0 |
TIME7 | 生产者C拿到锁,因为C是从等待中被唤醒的,所以从wait之后开始执行,进行生产 ,并唤醒B、D | 1 |
TIME8 | 生产者C再次拿到锁,仓库已满,C进入等待 | 1 |
TIME9 | 生产者A拿到锁,因为A是从等待中被唤醒的,所以从wait之后执行,进行生产,并唤醒C | 2(发生虚假唤醒) |
为什么会产生这种原因呢?因为我们在进行是否wait是用的是if循环,if语句只执行一次,当等待中的锁被唤醒,它将直接继续向下执行,如果使用while,就会循环判断是否满足条件,如果不满足将继续进入等待。
二、正确打开方式
代码如下:
public class TestProducerAndCustomer {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
Producer producer2 = new Producer(clerk);
Consumer consumer2 = new Consumer(clerk);
new Thread(producer, "生产者A").start();
new Thread(consumer, "消费者B").start();
new Thread(producer2, "生产者C").start();
new Thread(consumer2, "消费者D").start();
}
}
// 店员类:负责进货和售货
class Clerk {
private int num = 0; //店里当前的货物量
public synchronized void get() { //店员进货 每次进货一个(生产者)
while (num >= 1) {
System.out.println(Thread.currentThread().getName() + " 库存已满,无法进货");
try {
this.wait();
System.out.println(Thread.currentThread().getName() + " wait后剩余步骤");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " : " + (++num));
this.notifyAll();
}
public synchronized void sale() { //店员卖货 每次卖掉一个货(消费者)
while (num <= 0) {
System.out.println(Thread.currentThread().getName() + " 库存已空,无法卖货");
try {
this.wait();
System.out.println(Thread.currentThread().getName() + " wait后剩余步骤");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " : " + (--num));
this.notifyAll();
}
}
// 生产者 可以有很多生产者卖货给这个店员
class Producer implements Runnable {
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; 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 < 20; i++) {
clerk.sale();
}
}
}
总结
在进行wait操作的条件判断时,注意判断语句的选择,以防出现虚假唤醒。
Good Good Study,Day Day Up!