• 线程的虚假唤醒情况


    ​ 多线程并发操作一直都是学习和工作过程中的难点,一般而言,在多个线程共享资源时,我们通常会使用synchronized代码块的同步,并通过wait()、notify()和notifyAll()来唤醒或者等待线程(这三个方法必须使用在同步代码块或同步方法中,被同步监视器调用,否则会抛出异常)。

    还是通过经典的生产者和消费者案例引出虚假唤醒的问题

    public class SpuriousWakeupDemo {
        public static void main(String[] args) {
            ShareData resources = new ShareData();
            new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    try {
                        resources.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "producer").start();
            new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    try {
                        resources.decrement();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }, "consumer").start();
        }
    }
    
    /**
     * 资源类
     */
    class ShareData{
        private int number = 0;//初始值为零的一个变量
    
        public synchronized void increment() throws InterruptedException {
            //1判断
            if (number != 0) {
                this.wait();
            }
            //2干活
            ++number;
            System.out.println(Thread.currentThread().getName() + "	" + number);
            //3通知
            this.notifyAll();
        }
    
        public synchronized void decrement() throws InterruptedException {
            // 1判断
            if (number == 0) {
                this.wait();
            }
            // 2干活
            --number;
            System.out.println(Thread.currentThread().getName() + "	" + number);
            // 3通知
            this.notifyAll();
        }
    }
    

    多次测试结果如下:

    image-20201004193620658

    在main方法中通过匿名内部类的方式创建了两个线程,一个作为生产者,一个作为消费者。上面这个代码正常运行,也并没有出现什么安全问题。但是我们却忽略了一个重要的点!!过我们们改动代码,在main方法中多加如两个生产者和消费者如下:

    public class SpuriousWakeupDemo {
        public static void main(String[] args) {
            ShareData resources = new ShareData();
            new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    try {
        
                        resources.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "producer1").start();
            new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    try {
                        resources.decrement();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }, "consumer1").start();
             new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    try {
                        resources.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "producer2").start();
            new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    try {
                        resources.decrement();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }, "consumer2").start();
        }
    }
    

    多次测试,出现了以下结果:

    image-20201004193942276

    我们发现ShareData类中的共享变量number即使在不等于0的情况下依然在自增,increment()方法明明已经通过了synchronized进行了加锁,并且在方法内部做了判断当number!=0就将线程等待,为什么还是会出现number>0的情况?

    解决上述问题很简单,就是将if判断改为while判断,上述问题就再也没有出现。

    class ShareData{
        private int number = 0;//初始值为零的一个变量
    
        public synchronized void increment() throws InterruptedException {
            //1判断
            while (number != 0) {
                this.wait();
            }
            //2干活
            ++number;
            System.out.println(Thread.currentThread().getName() + "	" + number);
            //3通知
            this.notifyAll();
        }
    
        public synchronized void decrement() throws InterruptedException {
            // 1判断
            while (number == 0) {
                this.wait();
            }
            // 2干活
            --number;
            System.out.println(Thread.currentThread().getName() + "	" + number);
            // 3通知
            this.notifyAll();
        }
    }
    

    这个问题的关键就在于当线程被唤醒时,从哪里运行。当我们使用if作为条件判断时,分别在wait()方法前后加两条打印语句

    class ShareData{
        private int number = 0;//初始值为零的一个变量
    
        public synchronized void increment() throws InterruptedException {
            //1判断
            if (number != 0) {
                System.out.println("生产线程"+Thread.currentThread().getName()+"准备休眠");
                this.wait();
                System.out.println("生产线程"+Thread.currentThread().getName()+"休眠结束");
            }
            //2干活
            ++number;
            System.out.println(Thread.currentThread().getName() + "	" + number);
            //3通知
            this.notifyAll();
        }
    
        public synchronized void decrement() throws InterruptedException {
            // 1判断
            if (number == 0) {
                this.wait();
            }
            // 2干活
            --number;
            System.out.println(Thread.currentThread().getName() + "	" + number);
            // 3通知
            this.notifyAll();
        }
    }
    

    控制台打印结果如下:

    image-20201004203719676

    将if改为while条件后述情况就不会存在,由于是while()循环,所有被等待的线程在获取到cpu执行权利后一定会进行条件判断,不会出现两个生产者交替获得CPU执行权将number+1的情况(也不会出现两个消费者交替获得cpu执行权的情况)

    如图:

    image-20201004204345780

  • 相关阅读:
    为什么我们从Yarn切换到pnpm
    🔑 最佳密码长度是多少?
    vue + ArcGIS 地图应用系列二:加载地图
    vue + ArcGIS 地图应用系列一:arcgis api本地部署(开发环境)
    玩转 GitHub 的几个小技巧
    在 Array.some 中正确使用 async
    如何正确的在 Array.map 使用 async
    一道关于JavaScript 代码执行顺序的面试题解析
    Git 常用命令及应用这一篇就够了(新手向)
    VUE 子组件向父组件传值 , 并且触发父组件方法(函数)
  • 原文地址:https://www.cnblogs.com/myblogstart/p/13768354.html
Copyright © 2020-2023  润新知