虚假唤醒,是多线程编程里的一个概念。在wati()/notityAll()的使用场景中,存在虚假唤醒的情况,如果使用不当,可能会导致程序执行结果错误。
在生活中,也有一些司空见惯的例子。张三和李四哥俩去超市买卫生纸,由于暂时缺货,老板告知哥俩可以先预约着,等到货了通知哥俩来取货,于是俩兄弟便回去等待老板的通知。
1)张三、李四哥俩去超市买卫生纸
2)超市暂时缺货,告知哥俩回家等到货通知
3)卫生纸仅到货一提,通知哥俩速来
4)张三先到先得,李四后到哭了
预演完上面的场景,我们来看看代码的实现。
import lombok.SneakyThrows;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class ThreadSpuriousWakeupTest {
@SneakyThrows
public static void main(String[] args) {
Supermarket supermarket = new Supermarket();
CustomRunnable runnableA = new CustomRunnable(supermarket, "张三");
CustomRunnable runnableB = new CustomRunnable(supermarket, "李四");
Thread threadA = new Thread(runnableA);
Thread threadB = new Thread(runnableB);
threadA.start();
threadB.start();
TimeUnit.SECONDS.sleep(1);
supermarket.purchase();
}
static class Supermarket {
private List<Object> toiletPapers = new ArrayList<>();
private Object lock = new Object();
public void sale(String customer) throws InterruptedException {
synchronized (lock) {
if (toiletPapers.size() == 0) {
System.out.println("卫生纸暂时缺货," + customer + "请先回去等到货通知");
lock.wait();
}
try {
toiletPapers.remove(0);
System.out.println(customer + "先到先得,舒服了");
} catch (Exception e) {
System.out.println(customer + "没买到,哭了");
}
}
}
public void purchase() {
synchronized (lock) {
toiletPapers.add(new Object());
lock.notifyAll();
}
}
}
static class CustomRunnable implements Runnable {
private Supermarket supermarket;
private String customer;
public CustomRunnable(Supermarket supermarket, String customer) {
this.supermarket = supermarket;
this.customer = customer;
}
@SneakyThrows
@Override
public void run() {
supermarket.sale(customer);
}
}
}
执行结果:程序执行完成,与我们的预期结果不太匹配,预期结果应该是李四继续等待到货通知,实际上李四满心欢喜,最后却哭了。
这种情况,就是所谓的线程虚假唤醒,也就是本不应该被唤醒线程被唤醒了,导致程序执行结果错误。
那么我们应该怎么处理呢?
我们来看这段关键的代码:
public void sale(String customer) throws InterruptedException {
synchronized (lock) {
// 这里使用的if进行条件判断
if (toiletPapers.size() == 0) {
System.out.println("卫生纸暂时缺货," + customer + "请先回去等到货通知");
lock.wait();
}
try {
toiletPapers.remove(0);
System.out.println(customer + "先到先得,舒服了");
} catch (Exception e) {
System.out.println(customer + "没买到,哭了");
}
}
}
我们使用的if
进行条件判断,当收到线程唤醒通知时,线程就会在wait()
处唤醒,然后继续向下执行。
在这里,线程唤醒的时候,我们应该再进行一次判断,是否满足继续向下执行的条件,这样问题就比较清晰明了了,我们将if
判断改成wihle
判断即可。
public void sale(String customer) throws InterruptedException {
synchronized (lock) {
// 这里使用的while进行条件判断
while (toiletPapers.size() == 0) {
System.out.println("卫生纸暂时缺货," + customer + "请先回去等到货通知");
lock.wait();
}
try {
toiletPapers.remove(0);
System.out.println(customer + "先到先得,舒服了");
} catch (Exception e) {
System.out.println(customer + "没买到,哭了");
}
}
}
再来看看执行结果:
问题解决。