虚假唤醒
虚假唤醒就是当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用的。
下面看一个生产者消费者的例子
public static class Data {
private int num = 0;
public synchronized void increment() {
if (num > 0) {
try {
System.out.println(Thread.currentThread().getName() + "阻塞等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num++;
System.out.println(Thread.currentThread().getName() + ":" + num);
this.notifyAll();
}
public synchronized void decrement() {
if (num <= 0) {
try {
System.out.println(Thread.currentThread().getName() + "阻塞等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num--;
System.out.println(Thread.currentThread().getName() + ":" + num);
this.notifyAll();
}
}
上面的Data类有两个同步方法,increament方法是对num加1,decrement方法是对num减1.
两个都是同步方法,也就是说,我们期望num的值要么是0,要么是1.
通过下面代码,我们创建了两个生产者,两个消费者。
public class ProductConsume {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
for (int i=0; i<20; i++) {
data.increment();
}
}, "生产者线程A").start();
new Thread(() -> {
for (int i=0; i<20; i++) {
data.increment();
}
}, "生产者线程B").start();
new Thread(() -> {
for (int i=0; i<20; i++) {
data.decrement();
}
}, "消费者线程C").start();
new Thread(() -> {
for (int i=0; i<20; i++) {
data.decrement();
}
}, "消费者线程D").start();
}
}
看下运行的结果
从结果来看,跟我们的期望不太一样,竟然出现了num为2甚至为3的情况。
根据结果中每行的日志,我们逐步分析:
- 线程A先执行,进入同步代码块,此时,num初始值为0,满足加1的条件,所以加num = 1;
- 线程A执行了notifiAll方法,唤醒其他线程,但是A又继续抢到了cpu执行机会,继续执行第二次循环,这次由于num的值为1,所以线程A阻塞,并释放锁(wait方法释放锁这个动作很关键);
- 线程B获取锁,进入increment方法,判断num=1,所以线程B阻塞,并释放锁
- 线程C得到cpu执行机会,判断num=1,可以进行减1,并调notifyAll方法唤醒其他线程;
- 线程C继续抢到了cpu执行机会,判断num=0,进行阻塞,并释放锁;
- 线程B唤醒,对num加1,并调notifyAll方法唤醒其他线程;
- 线程B又抢到了cpu执行机会,判断num=1,所以进行wait;
- 线程A获取cpu的执行机会,而此时,线程A执行的进度在wait方法处,所以从wait方法后开始执行,所以对num进行了加1操作,导致num的值为2;
- 。。。
从上面分析我们知道了,线程A已经进入同步方法内,而且在第6步被notifyAll方法激活为可执行态,所以在第8步抢到cpu执行机会之后,就直接执行后面的代码,导致num增加1而没有判断num值的状态。
这就是虚假唤醒的例子。
为了避免虚假唤醒,可以将increment和decrement方法中的if判断改成while,这样在线程被执行的时候,就会重新判断num的状态,从而避免虚假唤醒问题出现。代码如下:
public static class Data {
private int num = 0;
public synchronized void increment() {
while (num > 0) {
try {
System.out.println(Thread.currentThread().getName() + "阻塞等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num++;
System.out.println(Thread.currentThread().getName() + ":" + num);
this.notifyAll();
}
public synchronized void decrement() {
while (num <= 0) {
try {
System.out.println(Thread.currentThread().getName() + "阻塞等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num--;
System.out.println(Thread.currentThread().getName() + ":" + num);
this.notifyAll();
}
}
运行结果如下:
结果符合预期。