版权声明:本文为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通知的泄露的更多相关文章

  1. java线程——notifyAll通知的泄露

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

  2. java并发编程(十一)线程间的通信notify通知的遗漏

    notify通知的遗漏很容易理解,即threadA还没开始wait的时候,threadB已经notify了,这样,threadB通知是没有任何响应的,当threadB退出synchronized代码块 ...

  3. 转:【Java并发编程】之十一:线程间通信中notify通知的遗漏(含代码)

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/17228213 notify通知的遗漏很容易理解,即threadA还没开始wait的时候,t ...

  4. 【Java并发编程】之十一:线程间通信中notify通知的遗漏

    notify通知的遗漏很容易理解,即threadA还没开始wait的时候,threadB已经notify了,这样,threadB通知是没有任何响应的,当threadB退出synchronized代码块 ...

  5. 二 Java利用等待/通知机制实现一个线程池

    接着上一篇博客的 一Java线程的等待/通知模型 ,没有看过的建议先看一下.下面我们用等待通知机制来实现一个线程池 线程的任务就以打印一行文本来模拟耗时的任务.主要代码如下: 1  定义一个任务的接口 ...

  6. 一 java线程的等待/通知模型

    java 中线程之间的通信问题,有这么一个模型:一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个过程开始于一个线程,而最终执行又是另一个线程.前者是生产者,后者就是消费者 ...

  7. JAVA 线程状态以及synchronized,wait,sleep,yield,notify,notifyAll

    java线程存在以下几种状态: 1: 创建状态(New):线程被new出来,还未调用start 2: 就绪状态(Runnable):又称为可执行状态,调用线程的start方法后,线程处于就绪状态,,线 ...

  8. Java线程间通信之wait/notify

    Java中的wait/notify/notifyAll可用来实现线程间通信,是Object类的方法,这三个方法都是native方法,是平台相关的,常用来实现生产者/消费者模式.我们来看下相关定义: w ...

  9. Java线程的wait(), notify()和notifyAll()

    Java线程生命周期 类java.lang.Thread包含一个静态的State enum用于定义每种可能的状态. 在任意的时间点, 线程会处于以下的状态之一: NEW – 新创建的线程, 还未启动( ...

随机推荐

  1. Python基础笔记:函数:调用函数、定义函数、函数的参数、递归函数

    一.定义一个求二元一次方程的根的函数 #Sublime Text import math def ee(a,b,c): delta=b*b-4*a*c if delta<0: return 'n ...

  2. 149-PHP大小写转换函数

    <?php $str='PHP is a very good programming language.'; //定义一个字符串 echo "未经处理的字符串:<br /> ...

  3. Spring 框架介绍

    Spring 框架介绍 Spring 框架模块 Spring开发环境搭建(Eclipse) 创建一个简单的Spring应用 Spring 控制反转容器(Inversion of Control – I ...

  4. SecureCRT打开文件中文乱码

    1.菜单:option(选项): 2.选择session options(会话选项): 3.打开的窗口中,点击Appearance(外观): 4.页面上:character encoding(字符编码 ...

  5. vue-router 二级路由(父子路由)

    使用二级路由 会显示父路由下面的子路由  且父子路由同时显示 因为父子同时显示  路由地址在同一级别/ 路由的显示模式有两种(都是为了减少数据库后台请求次数) #hash模式(#是特殊字符,很多场合不 ...

  6. Windows2008R2安装iis和iis下搭建web服务器(9.18 第七天)

    IIS internet information services 互联网信息服务微软开发的运行在windows中的互联网服务,提供了web.ftp.smtp服务 Windows server 200 ...

  7. Distributed--分布式架构

    如果我们期望实现一套严格满足ACID特性的分布式事务,很可能出现的情况就是在系统的可用性和严格一致性之间出现冲突. 在可用性和一致性之间,永远无法存在一个两全其美的方案. 从集中式到分布式 集中式系统 ...

  8. BZOJ:2815: [ZJOI2012]灾难

    题解: 构造灭绝树: x指向的点表示x的祖先死亡则x死亡 动态LCA: 可以用LCT维护或直接更新倍增数组 最后统计子树点的个数 坑: 我还不会序列型Toposort #include<iost ...

  9. Vue.js(2)- 过滤器

    概念:过滤器本质上就是一个函数,可被用作一些常见的文本格式化. 过滤器只可以用在两个地方:mustache 插值表达式和 v-bind 表达式. 过滤器应该被添加在 JavaScript 表达式的尾部 ...

  10. Aizu 2155 Magic Slayer 背包DP

    这是上上次对抗赛的题目了 其实现在发现整个代码从头到尾,都是用了背包,怪我们背包没深入学好. 比赛的时候,聪哥提出的一种思路是,预处理一下,背包出 ALL攻击 和 single攻击的 血量对应的最小花 ...