前提:
当只有一个生产者与消费者,也就是只有两个线程时,唤醒的永远时对方线程。
当只有一个生产者时和两个消费者,或者两个生产者与两个消费者时,唤醒的就是所有线程了,这样就会产生死锁和虚假唤醒。
多线程对多线程,必会有死锁和虚假唤醒问题。
比如下面的代码,初看正常,刚运行几次也正常,但多运行几次就出现问题了。大概率出现的问题是死锁,程序无穷无尽的卡住了。
我们来理解这个问题:C吃完了包子,此时包子为0个,C唤醒了厨师A和吃货B这两个线程。
因为A和B都醒了,A作为厨师去生产包子了。
B作为吃货一看包子为0个,就等着,而且是死等;当厨师A做完包子,唤醒大家去吃包子时,B还在死等,而此时虽然C也被唤醒了。
但C一看被锁住了,而这个吃货B却死等着不让,于是B和C就僵持住了。
相当于:餐厅里只有一个位置,B一看没有包子了,就死等;在B死等的时候,厨师A做好了一个包子并叫大家去吃。
C高兴的去吃包子,但位置被B占用了,而且B不知道厨师做好了包子,一直死等,一直占着位置,这样一来,就卡在B这里了,C和A都急死了。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Bun {
public static void main(String[] args) {
SteamedBun bun = new SteamedBun();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
bun.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "厨师A").start();//定义了一个厨师线程,生长包子
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
bun.eat();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "吃货B").start();//定义了一个吃货线程,吃包子
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
bun.eat();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "吃货C").start();//定义了一个吃货线程,吃包子
}
}
class SteamedBun {
private int num = 0;//包子数量
//生产者,包子铺的厨师
public synchronized void produce() throws InterruptedException {
while (num != 0) {
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName() + "生产了1个包子");
this.notifyAll();
}
//消费者-->吃包子的吃货
public synchronized void eat() throws InterruptedException {
while (num == 0) {
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName() + "吃了1个包子");
this.notifyAll();
System.out.println(Thread.currentThread().getName()+"叫厨师去做包子");
}
}
以上代码运行结果偶尔正常,偶尔发生本文开头提到的死锁。
下面的结果只有14行,就卡在14行不动了。
厨师A生产了1个包子
吃货B吃了1个包子
吃货B叫厨师去做包子
厨师A生产了1个包子
吃货B吃了1个包子
吃货B叫厨师去做包子
厨师A生产了1个包子
吃货C吃了1个包子
吃货C叫厨师去做包子
厨师A生产了1个包子
吃货B吃了1个包子
吃货B叫厨师去做包子
厨师A生产了1个包子
吃货C吃了1个包子
吃货C叫厨师去做包子