以下是一个案例,有一个店员,负责进货和卖货。进货生产,卖货消费。
当商品超过10件,生产等待,消费继续,当少于0件,消费等待,消费继续。
正常代码如下:
package com.atguigu.juc; /* * 生产者和消费者案例 */ public class TestProductorAndConsumer { public static void main(String[] args) { Clerk clerk = new Clerk(); Productor pro = new Productor(clerk); Consumer cus = new Consumer(clerk); new Thread(pro, "生产者 A").start(); new Thread(cus, "消费者 B").start(); } } //店员 class Clerk { private int product = 0; //进货 public synchronized void get(){//循环次数:0 if(product >= 10){ System.out.println("产品已满!"); try { this.wait(); } catch (InterruptedException e) { } } System.out.println(Thread.currentThread().getName() + " : " + ++product); this.notifyAll(); } //卖货 public synchronized void sale(){//product = 0; 循环次数:0 if(product <= 0){ System.out.println("缺货!"); try { this.wait(); } catch (InterruptedException e) { } } 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 < 20; i++) { try { Thread.sleep(200); } catch (InterruptedException e) { } 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(); } } }
运行结果:
很和谐没问题!,生产者每次生产完就等待一下,导致消费者抢到资源,这样导致:0,1轮替。
但是,如果此时再假如一个生产者和消费者:
public class TestProductorAndConsumer { public static void main(String[] args) { Clerk clerk = new Clerk(); Productor pro = new Productor(clerk); Consumer cus = new Consumer(clerk); new Thread(pro, "生产者 A").start(); new Thread(cus, "消费者 B").start(); new Thread(pro, "生产者 C").start(); new Thread(cus, "消费者 D").start(); } }
此时运行结果:
可以看到,非常离谱!生产者数量为负数,并且一直没有停止的样子。
分析:
假如最开始是缺货状态,消费者B和D进入都是进入等待的,此时一个生产者抢到资源,进行生产,完事生产了一件,
两个消费者同时唤醒,唤醒了之后,每个消费者都继续下面代码,也就是wait下面的--product,导致数量为负数。
这个时候两个消费者再次进入当然还是等待,一个生产者再次进入,当然效果和上面一样,再次数量在-1的基础上,-1,-2。
这种现象叫做虚假唤醒。
为了解决这个,其实JDK中已经说明了,对于wait方法的使用,必须始终放在while循环中。
每次线程被唤醒之后都得重新进入循环,而不是直接执行下面的--或者++操作。
修改如下:
把上面的if换成while即可:
package com.atguigu.juc; /* * 生产者和消费者案例 */ public class TestProductorAndConsumer { public static void main(String[] args) { Clerk clerk = new Clerk(); Productor pro = new Productor(clerk); Consumer cus = new Consumer(clerk); new Thread(pro, "生产者 A").start(); new Thread(cus, "消费者 B").start(); new Thread(pro, "生产者 C").start(); new Thread(cus, "消费者 D").start(); } } //店员 class Clerk { private int product = 0; //进货 public synchronized void get(){//循环次数:0 while(product >= 10){//为了避免虚假唤醒问题,应该总是使用在循环中 System.out.println("产品已满!"); try { this.wait(); } catch (InterruptedException e) { } } System.out.println(Thread.currentThread().getName() + " : " + ++product); this.notifyAll(); } //卖货 public synchronized void sale(){//product = 0; 循环次数:0 while(product <= 0){ System.out.println("缺货!"); try { this.wait(); } catch (InterruptedException e) { } } 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 < 20; i++) { try { Thread.sleep(200); } catch (InterruptedException e) { } 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(); } } }