生产者消费者案例-虚假唤醒
正常的生产者消费者案例
我们测试的即是唤醒机制,如果没有等待和唤醒,则会出现生产了没有线程消费,消费完了,没有线程生产的现象,这种情况这里不进行验证,我们test wait()和notifyAll()的问题
//1 先定义一个售货员角色类
class Saler {
private int product = 0;//产品数量
public synchronized void get() { //进货
if (product >= 5) { //5个为货满
System.out.println("产品已满");
try {
this.wait();
} catch (InterruptedException e) {
}
} else {
System.out.println(Thread.currentThread().getName() + " : " + ++product);
this.notifyAll();
}
}
public synchronized void sale() { //卖货
if (product <= 0) {
System.out.println("缺货");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println(Thread.currentThread().getName() + " : " + --product);
this.notifyAll();
}
}
}
//2 定义1个生产者类
class Pro implements Runnable {
private Saler saler;
public Pro(Saler saler) {
this.saler = saler;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
saler.get();
}
}
}
//3 定义1个消费者类
class Con implements Runnable {
private Saler saler;
public Con(Saler saler) {
this.saler = saler;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
saler.sale();
}
}
}
//4 测试
public class Test {
public static void main(String[] args) {
Saler saler = new Saler();
Pro pro = new Pro(saler);
Con cus = new Con(saler);
new Thread(pro, "生产者 A").start();
new Thread(cus, "消费者 B").start();
}
}
// 此时大概会有如下结果,我们到生产消费正常
生产者 A : 1
生产者 A : 2
生产者 A : 3
生产者 A : 4
生产者 A : 5
产品已满
消费者 B : 4
消费者 B : 3
消费者 B : 2
消费者 B : 1
消费者 B : 0
缺货
生产者 A : 1
生产者 A : 2
生产者 A : 3
生产者 A : 4
消费者 B : 3
消费者 B : 2
消费者 B : 1
消费者 B : 0
//如果把货满设置为1,则会出现交替的现象
生产者 A : 1
产品已满
消费者 B : 0
缺货
生产者 A : 1
产品已满
消费者 B : 0
缺货
生产者 A : 1
产品已满
消费者 B : 0
缺货
生产者 A : 1
产品已满
消费者 B : 0
缺货
生产者 A : 1
产品已满
消费者 B : 0
缺货
给生产者设置延时
run方法里添加200ms的延时,这时生产者会慢于消费者,货满仍然为1(为了明显显示效果)
class Pro implements Runnable {
private Saler saler;
public Pro(Saler saler) {
this.saler = saler;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
saler.get();
}
}
}
public class Test {
public static void main(String[] args) {
Saler saler = new Saler();
Pro pro = new Pro(saler);
Con cus = new Con(saler);
new Thread(pro, "生产者 A").start();
new Thread(cus, "消费者 B").start();
}
}
//结果为 可以看到最后一次生产的产品没有消费,且程序没有停止
缺货
生产者 A : 1
消费者 B : 0
缺货
生产者 A : 1
消费者 B : 0
缺货
生产者 A : 1
消费者 B : 0
缺货
生产者 A : 1
消费者 B : 0
缺货
生产者 A : 1
消费者 B : 0
生产者 A : 1
产品已满
//原因分析 如下,消费进的this.wait()后, 再次唤醒,由于else的存在,再次被唤醒后,则不会执行--product操作,
//消费者会提前结束循环,此时,生产者生的数据则不会继续被消费,故会一直处于wait状态
//为了解决这个问题,就该把else去掉
if (product <= 0) {
System.out.println("缺货");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println(Thread.currentThread().getName() + " : " + --product);
this.notifyAll();
}
//则Saler如下
class Saler {
private int product = 0;
public synchronized void get() {//循环次数:0
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 sale() {//product = 0; 循环次数:0
if (product <= 0) {
System.out.println("缺货");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " : " + --product);
this.notifyAll();
}
}
多个生产者消费者
能过以上的处理我们成功解决了上述问题
但当存在多个生产者消费者是,会出现新的问题,我们继续分析一下
public class Test {
public static void main(String[] args) {
Saler saler = new Saler();
Pro pro = new Pro(saler);
Con cus = new Con(saler);
new Thread(pro, "生产者 A").start();
new Thread(cus, "消费者 B").start();
new Thread(pro, "生产者 C").start();
new Thread(cus, "消费者 D").start();
}
}
//结果如下 发现出现了负数
缺货
缺货
生产者 A : 1
消费者 B : 0
缺货
消费者 D : -1
缺货
生产者 C : 0
消费者 D : -1
缺货
消费者 B : -2
缺货
消费者 D : -3
缺货
消费者 B : -4
缺货
消费者 D : -5
缺货
消费者 B : -6
缺货
消费者 D : -7
缺货
消费者 B : -8
缺货
消费者 D : -9
缺货
消费者 B : -10
缺货
消费者 D : -11
缺货
消费者 B : -12
缺货
消费者 D : -13
缺货
消费者 B : -14
缺货
消费者 D : -15
缺货
消费者 B : -16
缺货
消费者 D : -17
消费者 B : -18
生产者 A : -17
生产者 C : -16
生产者 A : -15
生产者 C : -14
生产者 A : -13
生产者 C : -12
生产者 C : -11
生产者 A : -10
生产者 C : -9
生产者 A : -8
生产者 C : -7
生产者 A : -6
生产者 C : -5
生产者 A : -4
生产者 C : -3
生产者 A : -2
生产者 A : -1
生产者 C : 0
//原因分析 假设2个消费者同时抢占资源
//当1个消费者抢占锁后,发现product==0,则wait()并释放锁, 此时另一个消费者抢占资源,发现product==0,也wait()并释放锁
//此时当生产者执行notifyAll()后, 同时唤醒2个消费者,都执行--product操作,即产生负数情况
//这就叫做虚假唤醒
//解决方案,将if替换为while, 被唤醒后重新判断product值(jdk api中有说明)
class Saler {
private int product = 0;
public synchronized void get() {//循环次数:0
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 sale() {//product = 0; 循环次数:0
while (product <= 0) {
System.out.println("缺货");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " : " + --product);
this.notifyAll();
}
}
//结果如下 即出现正确的交替结果
缺货
缺货
生产者 A : 1
消费者 B : 0
缺货
缺货
生产者 C : 1
消费者 B : 0
缺货
缺货
生产者 C : 1
消费者 B : 0
缺货
缺货
生产者 A : 1
消费者 B : 0
缺货
缺货
生产者 C : 1
消费者 B : 0
缺货
缺货
生产者 A : 1
消费者 B : 0
缺货
缺货
生产者 C : 1
消费者 B : 0
缺货
生产者 A : 1
消费者 D : 0
缺货
缺货
生产者 A : 1
消费者 D : 0
缺货
缺货
生产者 C : 1
消费者 D : 0
缺货
缺货
生产者 A : 1
消费者 D : 0
缺货
缺货
生产者 C : 1
消费者 D : 0
缺货
缺货
生产者 A : 1
消费者 D : 0
缺货
缺货
生产者 C : 1
消费者 D : 0
缺货
缺货
生产者 A : 1
消费者 D : 0
缺货
生产者 C : 1
消费者 B : 0
缺货
缺货
生产者 A : 1
消费者 B : 0
缺货
缺货
生产者 C : 1
消费者 B : 0
缺货
生产者 A : 1
消费者 D : 0
缺货
生产者 C : 1
消费者 D : 0