zoukankan      html  css  js  c++  java
  • 并发编程答疑解惑之我从睡眠中醒来后,从哪里开始,又将怎么执行

    前言和结论

    今天重新把昨晚的线程同步面试题做一遍时,发现实际情况运行下来时,线程一直不同步。后来经过不断测试,发现自己的一个误区。之前一直以为,线程如果被唤醒后再次执行时,会从头开始运行这个线程,也就是重新运行Runnable中的run()方法;而实际情况是,被唤醒并且被执行的线程是从上次阻塞的位置从下开始运行,也就是从wait()方法后开始执行。同时又对另一个问题进行了验证,为什么要用while判断循环条件,我用if不行么,前几天总是在想,于是决定动手验证下,实践才是检验真理的唯一标准呀,这里先把我验证的结论贴上:用if判断的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,wait之后的代码执行完后,会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。

    代码实例1:

    这里使用ReentrantLock进行验证,代码比较简单可直接复制到编译器里运行调试即可,这个例子先用来验证被唤醒后从哪里开始执行:

    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class AwaitSignal {
        private static ReentrantLock lock = new ReentrantLock();
        private static Condition condition = lock.newCondition();
        private static volatile boolean flag = false;
    
        public static void main(String[] args) {
            Thread waiter = new Thread(new waiter());
            waiter.start();
            Thread signaler = new Thread(new signaler());
            signaler.start();
        }
    
        static class waiter implements Runnable {
    
            @Override
            public void run() {
                lock.lock();
                System.out.println("我获取锁以后1");
                try {
                    System.out.println("我进入try块里2");
                    while (!flag) {
                        System.out.println(Thread.currentThread().getName() + "当前条件不满足等待");
                        try {
                            System.out.println("我准备睡觉觉啦3。。。-----waiter");
                            condition.await();
                            System.out.println("我被叫醒后4。。。-------waiter");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(Thread.currentThread().getName() + "接收到通知条件满足");
                } finally {
                    lock.unlock();
                }
            }
        }
    
        static class signaler implements Runnable {
    
            @Override
            public void run() {
                lock.lock();
                try {
                    flag = true;
                    System.out.println("我要叫醒你啦,你个小兔崽子睡睡睡------------signaler");
                    condition.signalAll();
                } finally {
                    lock.unlock();
                }
            }
        }
    }
    

    代码执行结果和分析:


    简单分析:main方法中首先创建了waiter线程并调用,然后创建signaler线程调用,所以基本上都是waiter线程先执行,第一步我获取锁以后1,第二步,我进入try块里2,第三步while(!flag)为true,进入代码块,条件不满足,准备睡觉觉3,调用await进入休息室,此时signaler线程获取锁,并将flag设置为true,打印我要叫醒你啦小兔崽子,waiter醒来之后,首先接着await后的代码执行,打印我被叫醒后4,接着会再次进入while循环判断,此时已经不满足条件,会跳出循环,接下来打印"接收到通知条件满足",这里的代码逻辑一定要搞清楚,有助于理解。

    代码实例2:

    接下来我们进行if和while的验证,其实只要明白了上面的执行步骤,这里也清楚地,只是验证下结果:我们将signaler中的flag改为false,分别验证while和if的情况:
    flag为false并用if验证的结果:

    这里flag为false,被唤醒后条件并不满足,但是仍然继续向下执行代码逻辑,会存在虚假唤醒问题,就像你去大宝剑按摩按摩脚底板,这时没技师,你先在休息区等待,过了一会有人通知你可以啦,但是你起身过去,等着按摩脚底板时,发现仍然没有闲暇的技师,于是你愤怒离开,发誓再也不来,发奋学习,从此损失了一个顾客,为国家少了税收,所以不能用if
    flag为false并用while验证的结果:

    我们这里用while则很好的避免了上述问题,通知你有技师,然后醒来后,你又确认了下,发现还没有,于是你并没有起身过去,而是继续休息。
    2个结果对比,应该很清楚了吧大家。

    艾欧尼亚,昂扬不灭,为了更美好的明天而战(#^.^#)
  • 相关阅读:
    GO-GRPC实践(二) 增加拦截器,实现自定义context(带request_id)、recover以及请求日志打印
    第六章-堆
    第五章-本地方法接口和本地方法栈
    第四章-虚拟机栈
    第三章-运行时数据区及程序计数器
    04-再谈类的加载器
    03-类的加载过程(类的生命周期)详解
    1.编程入门
    SpringBoot 整合 SpringSecurity 梳理
    pip版本过低无法升级问题
  • 原文地址:https://www.cnblogs.com/lovelywcc/p/13947995.html
Copyright © 2011-2022 走看看