• java线程——notify通知的泄露


    版权声明:本文为CSDN博主「兰亭风雨」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/ns_code/article/details/17228213

    一,什么是Notify通知的泄露?

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

    二,实例

    package com.itheima.gan;
    
    public class MessedNotify {
        //创建一个私有的变量,用来当锁
        private Object proceedLock;
        
        //创建一个构造方法,方法里面给私有变量赋值
        public MessedNotify() {
            System.out.println(Thread.currentThread().getName()+" 进入MessedNotify()方法");
            proceedLock=new Object();
        }
        
        //方法1
        public void waitToProceed() throws InterruptedException {
            System.out.println(Thread.currentThread().getName()+" 进入 waitToProceed()方法");
            
            synchronized (proceedLock) {
                System.out.println(Thread.currentThread().getName()+" 进入 waitToProceed()方法后执行开始执行wait方法 ");
                //调用方法
                proceedLock.wait();
                System.out.println(Thread.currentThread().getName()+"  从in waitToProceed()-从wait方法返回");
            }
            
            System.out.println(Thread.currentThread().getName()+"in waitToProceed() -离开");
        }
        
        //方法2
        public void proceed() {
            System.out.println(Thread.currentThread().getName()+" in proceed()-enterd");
            
            synchronized (proceedLock) {
                System.out.println(Thread.currentThread().getName()+" in proceed() -调用唤醒方法");
                //唤醒沉睡的线程
                proceedLock.notifyAll();
                System.out.println(Thread.currentThread().getName()+" in prooceed() -从唤醒方法处返回");
            }
            
            System.out.println(Thread.currentThread().getName()+" in proceed() -leaving");
        }
        
        //主方法
        public static void main(String[] args) {
            //创建一个对象
            final MessedNotify mn =new MessedNotify();
            
            //创建两个线程,访问资源
            
            //创建一个线程A
            Runnable runA=new Runnable() {
                @Override
                public void run() {
                    //先休眠1000ms,大于runB中的500ms,为了使notify先执行
                    try {
                        Thread.sleep(1000);
                        mn.waitToProceed();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    
                }
            };
            
            Thread threadA =new Thread(runA,"threadA");
            //线程开始
            threadA.start();
            
            
            //创建一个线程B
            Runnable runB=new Runnable() {
                @Override
                public void run() {
                    //先休眠500ms,大于runB中的500ms,为了使notify先执行
                    try {
                        Thread.sleep(500);
                        mn.proceed();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    
                }
            };
            
            Thread threadB =new Thread(runB,"threadB");
            //线程开始
            threadB.start();
            
            
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
            //试图打断wait阻塞
            System.out.println(Thread.currentThread().getName()+"about to invoke interrupt() on threadA");
            threadA.interrupt();
        }
    }

      

      运行的结果:

          

          

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

    三,解决notify通知泄露的问题?

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

    package com.itheima.gan;
    
    public class MessedNotifyFix {
        //创建一个私有的变量,用来当锁
        private Object proceedLock;
        
        //创建一个标识位,用来判断线程是否需要等待
        private boolean okToProceed;
        
        
        //创建一个构造方法,方法里面给私有变量赋值
        public MessedNotifyFix() {
            System.out.println(Thread.currentThread().getName()+" 进入MessedNotifyFix()构造方法");
            proceedLock=new Object();
            //初始化标识位
            okToProceed=false;
        }
        
        //方法1
        public void waitToProceed() throws InterruptedException {
            
            System.out.println(Thread.currentThread().getName()+" 进入 waitToProceed()方法");
            
            synchronized (proceedLock) {
                System.out.println(Thread.currentThread().getName()+"进入Lock资源");
                //while进行循环判断,不用if的原因是应为为了防止早期通知
                while(okToProceed == false) {
                    System.out.println(Thread.currentThread().getName()+" 进入Lock资源后执行开始执行wait方法 ");
                    //调用方法
                    proceedLock.wait();
                    System.out.println(Thread.currentThread().getName()+"  从wait方法返回");
                }
                System.out.println(Thread.currentThread().getName()+"离开Lock资源");
            }
            
            System.out.println(Thread.currentThread().getName()+" 线程从 waitToProceed()方法离开");
        }
        
        
        
        //方法2
        public void proceed() {
            System.out.println(Thread.currentThread().getName()+" 线程进入 proceed()方法");
            
            synchronized (proceedLock) {
                System.out.println(Thread.currentThread().getName()+"进入proceedLock资源");
                
                //在唤醒沉睡的线程前先是标识位为true,这样就不会出现通知泄露的情况,也不会是线程在wait处阻塞,
                //因为线程在标识位位false时才会进入wait方法。
                okToProceed=true;
                System.out.println(Thread.currentThread().getName()+" in proceed()调用唤醒方法");
                //唤醒沉睡的线程
                proceedLock.notifyAll();
                System.out.println(Thread.currentThread().getName()+" in prooceed()从唤醒方法处返回");
            
                
                System.out.println(Thread.currentThread().getName()+"离开proceedLock资源");
            }
            
            System.out.println(Thread.currentThread().getName()+" 线程从 proceed()离开");
        }
        
        //主方法
        public static void main(String[] args) {
            //创建一个对象
            final MessedNotifyFix mn =new MessedNotifyFix();
            
            //创建两个线程,访问资源
            
            //创建一个线程A
            Runnable runA=new Runnable() {
                @Override
                public void run() {
                    //先休眠1000ms,大于runB中的500ms,为了使notify先执行
                    try {
                        Thread.sleep(1000);
                        mn.waitToProceed();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    
                }
            };
            
            Thread threadA =new Thread(runA,"threadA");
            //线程开始
            threadA.start();
            
            
            //创建一个线程B
            Runnable runB=new Runnable() {
                @Override
                public void run() {
                    //先休眠500ms,大于runB中的500ms,为了使notify先执行
                    try {
                        Thread.sleep(500);
                        mn.proceed();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    
                }
            };
            
            Thread threadB =new Thread(runB,"threadB");
            //线程开始
            threadB.start();
            
            
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
            //试图打断wait阻塞
            System.out.println(Thread.currentThread().getName()+"about to invoke interrupt() on threadA");
            threadA.interrupt();
        }
    }

      

      运行结果:

            

      注意:代码中加了注释的部分,在threadB进行通知(既调用notify方法之前)之前,先将okToProceed置为true,这样如果threadA将通知遗漏,那么就不会进入while循环,也便不会执行wait方法,线程也就不会阻塞。如果通知没有被遗漏,wait方法返回后,okToProceed已经被置为true,下次while循环判断条件不成立,便会退出循环。(先将标识位设置为false,那么如果线程B没有先执行,那么标识位依旧位false,线程A就会先执行,进入wait方法,然后线程B执行,将标识位设置为true,并且唤醒线程A,线程A退出wait()方法,继续执行while循环,while条件不成立,退出while)。

       

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


  • 相关阅读:
    解决文字溢出,换行等问题
    js获取年、月、日、时、分、秒
    JQuery EasyUI DataGrid动态合并单元格
    JQuery EasyUI Combobox联动
    JQuery EasyUI 读取设置input
    JQuery EasyUI DataGrid获取当前行索引及快速清空
    jQuery EasyUI combobox多选及赋值
    JQuery EasyUI DataGrid 、tree查询
    HTML元素ID和Name区别
    JQuery EasyUI之DataGrid列名和数据列分别设置不同对齐方式(转)
  • 原文地址:https://www.cnblogs.com/zhilili/p/11969548.html
Copyright © 2020-2023  润新知