zoukankan      html  css  js  c++  java
  • 【Java并发编程】之十一:线程间通信中notify通知的遗漏

    notify通知的遗漏很容易理解,即threadA还没开始wait的时候,threadB已经notify了,这样,threadB通知是没有任何响应的,当threadB退出synchronized代码块后,threadA再开始wait,便会一直阻塞等待,直到被别的线程打断。

    遗漏通知的代码

    ​ 下面给出一段代码演示通知是如何遗漏的,如下:

    public class MissedNotify extends Object {  
        private Object proceedLock;  
      
        public MissedNotify() {  
            print("in MissedNotify()");  
            proceedLock = new Object();  
        }  
      
        public void waitToProceed() throws InterruptedException {  
            print("in waitToProceed() - entered");  
      
            synchronized ( proceedLock ) {  
                print("in waitToProceed() - about to wait()");  
                proceedLock.wait();  
                print("in waitToProceed() - back from wait()");  
            }  
      
            print("in waitToProceed() - leaving");  
        }  
      
        public void proceed() {  
            print("in proceed() - entered");  
      
            synchronized ( proceedLock ) {  
                print("in proceed() - about to notifyAll()");  
                proceedLock.notifyAll();  
                print("in proceed() - back from notifyAll()");  
            }  
      
            print("in proceed() - leaving");  
        }  
      
        private static void print(String msg) {  
            String name = Thread.currentThread().getName();  
            System.out.println(name + ": " + msg);  
        }  
      
        public static void main(String[] args) {  
            final MissedNotify mn = new MissedNotify();  
      
            Runnable runA = new Runnable() {  
                    public void run() {  
                        try {  
                            //休眠1000ms,大于runB中的500ms,  
                            //是为了后调用waitToProceed,从而先notifyAll,后wait,  
                            //从而造成通知的遗漏  
                            Thread.sleep(1000);  
                            mn.waitToProceed();  
                        } catch ( InterruptedException x ) {  
                            x.printStackTrace();  
                        }  
                    }  
                };  
      
            Thread threadA = new Thread(runA, "threadA");  
            threadA.start();  
      
            Runnable runB = new Runnable() {  
                    public void run() {  
                        try {  
                            //休眠500ms,小于runA中的1000ms,  
                            //是为了先调用proceed,从而先notifyAll,后wait,  
                            //从而造成通知的遗漏  
                            Thread.sleep(500);  
                            mn.proceed();  
                        } catch ( InterruptedException x ) {  
                            x.printStackTrace();  
                        }  
                    }  
                };  
      
            Thread threadB = new Thread(runB, "threadB");  
            threadB.start();  
      
            try {   
                Thread.sleep(10000);  
            } catch ( InterruptedException x ) {}  
      
            //试图打断wait阻塞  
            print("about to invoke interrupt() on threadA");  
            threadA.interrupt();  
        }  
    }
    

    执行结果如下:

    img

    ​ 分析:由于threadB在执行mn.proceed()之前只休眠了500ms,而threadA在执行mn.waitToProceed()之前休眠了1000ms,因此,threadB会先苏醒,继而执行mn.proceed(),获取到proceedLock的对象锁,继而执行其中的notifyAll(),当退出proceed()方法中的synchronized代码块时,threadA才有机会获取proceedLock的对象锁,继而执行其中的wait()方法,但此时notifyAll()方法已经执行完毕,threadA便漏掉了threadB的通知,便会阻塞下去。后面主线程休眠10秒后,尝试中断threadA线程,使其抛出InterruptedException。

    修正后的代码

    ​ 为了修正MissedNotify,需要添加一个boolean指示变量,该变量只能在同步代码块内部访问和修改。修改后的代码如下:

    public class MissedNotifyFix extends Object {  
        private Object proceedLock;  
        //该标志位用来指示线程是否需要等待  
        private boolean okToProceed;  
      
        public MissedNotifyFix() {  
            print("in MissedNotify()");  
            proceedLock = new Object();  
            //先设置为false  
            okToProceed = false;  
        }  
      
        public void waitToProceed() throws InterruptedException {  
            print("in waitToProceed() - entered");  
      
            synchronized ( proceedLock ) {  
                print("in waitToProceed() - entered sync block");  
                //while循环判断,这里不用if的原因是为了防止早期通知  
                while ( okToProceed == false ) {  
                    print("in waitToProceed() - about to wait()");  
                    proceedLock.wait();  
                    print("in waitToProceed() - back from wait()");  
                }  
      
                print("in waitToProceed() - leaving sync block");  
            }  
      
            print("in waitToProceed() - leaving");  
        }  
      
        public void proceed() {  
            print("in proceed() - entered");  
      
            synchronized ( proceedLock ) {  
                print("in proceed() - entered sync block");  
                //通知之前,将其设置为true,这样即使出现通知遗漏的情况,也不会使线程在wait出阻塞  
                okToProceed = true;  
                print("in proceed() - changed okToProceed to true");  
                proceedLock.notifyAll();  
                print("in proceed() - just did notifyAll()");  
      
                print("in proceed() - leaving sync block");  
            }  
      
            print("in proceed() - leaving");  
        }  
      
        private static void print(String msg) {  
            String name = Thread.currentThread().getName();  
            System.out.println(name + ": " + msg);  
        }  
      
        public static void main(String[] args) {  
            final MissedNotifyFix mnf = new MissedNotifyFix();  
      
            Runnable runA = new Runnable() {  
                    public void run() {  
                        try {  
                            //休眠1000ms,大于runB中的500ms,  
                            //是为了后调用waitToProceed,从而先notifyAll,后wait,  
                            Thread.sleep(1000);  
                            mnf.waitToProceed();  
                        } catch ( InterruptedException x ) {  
                            x.printStackTrace();  
                        }  
                    }  
                };  
      
            Thread threadA = new Thread(runA, "threadA");  
            threadA.start();  
      
            Runnable runB = new Runnable() {  
                    public void run() {  
                        try {  
                            //休眠500ms,小于runA中的1000ms,  
                            //是为了先调用proceed,从而先notifyAll,后wait,  
                            Thread.sleep(500);  
                            mnf.proceed();  
                        } catch ( InterruptedException x ) {  
                            x.printStackTrace();  
                        }  
                    }  
                };  
      
            Thread threadB = new Thread(runB, "threadB");  
            threadB.start();  
      
            try {   
                Thread.sleep(10000);  
            } catch ( InterruptedException x ) {}  
      
            print("about to invoke interrupt() on threadA");  
            threadA.interrupt();  
        }  
    }  
    

    执行结果如下:

    img

    ​ 注意代码中加了注释的部分,在threadB进行通知之前,先将okToProceed置为true,这样如果threadA将通知遗漏,那么就不会进入while循环,也便不会执行wait方法,线程也就不会阻塞。如果通知没有被遗漏,wait方法返回后,okToProceed已经被置为true,下次while循环判断条件不成立,便会退出循环。

    ​ 这样,通过标志位和wait、notifyAll的配合使用,便避免了通知遗漏而造成的阻塞问题。

    总结:在使用线程的等待/通知机制时,一般都要配合一个boolean变量值(或者其他能够判断真假的条件),在notify之前改变该boolean变量的值,让wait返回后能够退出while循环(一般都要在wait方法外围加一层while循环,以防止早期通知),或在通知被遗漏后,不会被阻塞在wait方法处。这样便保证了程序的正确性。

  • 相关阅读:
    BZOJ 1040 (ZJOI 2008) 骑士
    BZOJ 1037 (ZJOI 2008) 生日聚会
    ZJOI 2006 物流运输 bzoj1003
    ZJOI 2006 物流运输 bzoj1003
    NOI2001 炮兵阵地 洛谷2704
    NOI2001 炮兵阵地 洛谷2704
    JLOI 2013 卡牌游戏 bzoj3191
    JLOI 2013 卡牌游戏 bzoj3191
    Noip 2012 day2t1 同余方程
    bzoj 1191 [HNOI2006]超级英雄Hero——二分图匹配
  • 原文地址:https://www.cnblogs.com/zailushang1996/p/8796060.html
Copyright © 2011-2022 走看看