线程虚假唤醒,你搞明白了吗?

1.概述

虚假唤醒,是多线程编程里的一个概念。在wati()/notityAll()的使用场景中,存在虚假唤醒的情况,如果使用不当,可能会导致程序执行结果错误。

2.场景预演

在生活中,也有一些司空见惯的例子。张三和李四哥俩去超市买卫生纸,由于暂时缺货,老板告知哥俩可以先预约着,等到货了通知哥俩来取货,于是俩兄弟便回去等待老板的通知。

1)张三、李四哥俩去超市买卫生纸

线程虚假唤醒,你搞明白了吗?_第1张图片

2)超市暂时缺货,告知哥俩回家等到货通知

线程虚假唤醒,你搞明白了吗?_第2张图片

3)卫生纸仅到货一提,通知哥俩速来

线程虚假唤醒,你搞明白了吗?_第3张图片

4)张三先到先得,李四后到哭了

线程虚假唤醒,你搞明白了吗?_第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);
        }
    }
}

执行结果:程序执行完成,与我们的预期结果不太匹配,预期结果应该是李四继续等待到货通知,实际上李四满心欢喜,最后却哭了。

这种情况,就是所谓的线程虚假唤醒,也就是本不应该被唤醒线程被唤醒了,导致程序执行结果错误。

线程虚假唤醒,你搞明白了吗?_第5张图片

那么我们应该怎么处理呢?

我们来看这段关键的代码:

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 + "没买到,哭了");
        }
    }
}

再来看看执行结果:

线程虚假唤醒,你搞明白了吗?_第6张图片

问题解决。

你可能感兴趣的:(java,java)