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方法处。这样便保证了程序的正确性。
java线程——notify通知的泄露的更多相关文章
- java线程——notifyAll通知的泄露
		
版权声明:本文为CSDN博主「兰亭风雨」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明.原文链接:https://blog.csdn.net/ns_code/ar ...
 - java并发编程(十一)线程间的通信notify通知的遗漏
		
notify通知的遗漏很容易理解,即threadA还没开始wait的时候,threadB已经notify了,这样,threadB通知是没有任何响应的,当threadB退出synchronized代码块 ...
 - 转:【Java并发编程】之十一:线程间通信中notify通知的遗漏(含代码)
		
转载请注明出处:http://blog.csdn.net/ns_code/article/details/17228213 notify通知的遗漏很容易理解,即threadA还没开始wait的时候,t ...
 - 【Java并发编程】之十一:线程间通信中notify通知的遗漏
		
notify通知的遗漏很容易理解,即threadA还没开始wait的时候,threadB已经notify了,这样,threadB通知是没有任何响应的,当threadB退出synchronized代码块 ...
 - 二   Java利用等待/通知机制实现一个线程池
		
接着上一篇博客的 一Java线程的等待/通知模型 ,没有看过的建议先看一下.下面我们用等待通知机制来实现一个线程池 线程的任务就以打印一行文本来模拟耗时的任务.主要代码如下: 1 定义一个任务的接口 ...
 - 一    java线程的等待/通知模型
		
java 中线程之间的通信问题,有这么一个模型:一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个过程开始于一个线程,而最终执行又是另一个线程.前者是生产者,后者就是消费者 ...
 - JAVA 线程状态以及synchronized,wait,sleep,yield,notify,notifyAll
		
java线程存在以下几种状态: 1: 创建状态(New):线程被new出来,还未调用start 2: 就绪状态(Runnable):又称为可执行状态,调用线程的start方法后,线程处于就绪状态,,线 ...
 - Java线程间通信之wait/notify
		
Java中的wait/notify/notifyAll可用来实现线程间通信,是Object类的方法,这三个方法都是native方法,是平台相关的,常用来实现生产者/消费者模式.我们来看下相关定义: w ...
 - Java线程的wait(), notify()和notifyAll()
		
Java线程生命周期 类java.lang.Thread包含一个静态的State enum用于定义每种可能的状态. 在任意的时间点, 线程会处于以下的状态之一: NEW – 新创建的线程, 还未启动( ...
 
随机推荐
- 如何创建一个Asp .Net Web Api项目
			
1.点击文件=>新建=>项目 2.创建一个Asp .NET Web项目 3.选择Empty,然后选中下面的MVC和Web Api,也可以直接选择Web Api选项,注意将身份验证设置为无身 ...
 - Nginx+uwsgi+django部署项目
			
nginx把请求转发给uwsgi,然后把uwsgi处理得到的结果返回给浏览器. 安装nginx: yum -y install gcc pcre-devel openssl-devel #安装Ngin ...
 - Mysql 终端中文显示乱码
			
查看编码 show variables like 'char%'; 结果 +--------------------------+--------+ | Variable_name | Value | ...
 - 留学Essay写作中常见的两类要求词盘点
			
写essay的时候,我们会常常因为各式各样的要求词而头疼:discuss,describing,evaluate,explain,等等,他们之间有何区别?如果你在思考这个问题,那么这篇文章就是为你写的 ...
 - 编码对象或者字串中包含Unicode字符怎样转换为中文
			
In [18]: c = '你好' In [20]: d = c.encode('unicode_escape') In [21]: d Out[21]: b'\\u4f60\\u597d' In [ ...
 - POJ 3096:Surprising Strings
			
Surprising Strings Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 6258 Accepted: 407 ...
 - JavaSE--日志
			
参考 https://www.cnblogs.com/hanszhao/p/9754419.html https://www.cnblogs.com/chenhongliang/p/5312517.h ...
 - 大数据高可用集群环境安装与配置(09)——安装Spark高可用集群
			
1. 获取spark下载链接 登录官网:http://spark.apache.org/downloads.html 选择要下载的版本 2. 执行命令下载并安装 cd /usr/local/src/ ...
 - 安装kubernetes遇见coredns坑
			
安装kubernetes遇见问题 kubectl describe pod coredns -n kube-system, 查看发现coredns readiness 一直unhealthy, 并且一 ...
 - java基础——深拷贝和浅拷贝的区别
			
浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝. 深拷贝:对基本数据类型进行值传递,对引用数据类型船舰一个新的对象,并复制内容,这是深拷贝.