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();  }
}

执行结果如下:

​ 分析:由于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();  }
}  

执行结果如下:

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

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

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

转载于:https://www.cnblogs.com/zailushang1996/p/8796060.html

【Java并发编程】之十一:线程间通信中notify通知的遗漏相关推荐

  1. Java并发编程(04):线程间通信,等待/通知机制

    本文源码:GitHub·点这里 || GitEE·点这里 一.概念简介 1.线程通信 在操作系统中,线程是个独立的个体,但是在线程执行过程中,如果处理同一个业务逻辑,可能会产生资源争抢,导致并发问题, ...

  2. 【JUC并发编程03】线程间通信

    文章目录 3 线程间通信 3.1 synchronized 实现案例 3.2 虚假唤醒问题 3.3 Lock 实现案例 3 线程间通信 线程间通信有两种实现方法: 关键字 synchronized 与 ...

  3. Java并发编程--6.Exchanger线程间交换数据

    在两个线程之间定义同步点,当两个线程都到达同步点时,他们交换数据结构,因此第一个线程的数据结构进入到第二个线程中,第二个线程的数据结构进入到第一个线程中 在生产者-消费者情境模式中它包含了一个数缓冲区 ...

  4. 【Java从零到架构师第1季】【并发 Concurrent 03】线程间通信_ReentrantLock_线程池

    持续学习&持续更新中- 守破离 [Java从零到架构师第1季][并发 Concurrent 03]线程间通信_ReentrantLock_线程池 线程间通信 线程间通信-示例 可重入锁Reen ...

  5. java并发编程实践(2)线程安全性

    [0]README 0.0)本文部分文字描述转自:"java并发编程实战", 旨在学习"java并发编程实践(2)线程安全性" 的相关知识: 0.1)几个术语( ...

  6. Java进阶知识 - 多线程与线程间通信

    CountdownLatch, CyclicBarrier 分别适合什么场景呢? 大部分情况下, 子线程只需要关心自身执行的任务. 但在某些复杂的情况下, 需要使用多个线程来协同完成某个任务, 这就涉 ...

  7. java并发编程第一课 线程的创建、停止和状态变更

    开篇词: 由点及面,搭建你的 Java 并发知识网 你好,欢迎学习<Java 并发编程核心 78 讲>,我是讲师星星,一线互联网公司资深研发工程师,参与过集团内多个重点项目的设计与开发. ...

  8. Java基础学习——多线程(线程间通信-生产者消费者代码示例)

    JDK 1.5提供了多线程升级方案 将同步synchronized替换成了显示的Lock操作.可以实现唤醒.冻结指定的线程. Lock接口 Lock 实现提供了比使用 synchronized 方法和 ...

  9. Java并发编程—什么是线程?

    原文作者:way_more 原文地址:Java 多线程常见基础面试题总结,面试必看! 目录 一.什么是线程和进程? 二.简要描述线程与进程的关系 三.FAQ 一.什么是线程和进程? 1.1. 何为进程 ...

最新文章

  1. golang中的sync.WaitGroup
  2. GAN生成对抗网络-GAN原理与基本实现-去噪与卷积自编码器01
  3. 300来行代码实现最小Linux文件系统
  4. OpenGL+VS2013+WIN7(64)组态
  5. LeetCode(530)——二叉搜索树的最小绝对差(JavaScript)
  6. 我的世界java版_我的世界Java版1.15版本
  7. mysql比较两个表中count_mysql两个表统计查询问题?
  8. 1.1 OC类的认识
  9. 使用 kotlin 开发 android 遇到的问题
  10. Java 学习路线一条龙版
  11. Redisson分布式锁学习总结:RedissonMultiLock 如何同时锁住N个资源
  12. 阿里云盾(云安全)是什么?有什么作用?
  13. iphone xr xs_如何关闭iPhone X,XS和XR
  14. 阿里MNN推理框架相关介绍
  15. Retrofit简单使用(小白都会了)
  16. 离散数学复习三:广义交、广义并、蕴含式的真值表
  17. 计算机产业能否迅速发展,工业计算机得到了迅速的发展和全面的普及
  18. Windows下使用任务计划程序实现宽带开机自动拨号和断线自动重连
  19. STC15F104E (8脚单片机)实现蓝牙串口控制舵机转向
  20. 养成良好的习惯比投资知识更重要

热门文章

  1. [ActionScript 3.0] 获取TextFiled字符边框
  2. ASP.NET MVC+EF框架+EasyUI实现权限管理系列(18)-过滤器的使用和批量删除数据(伪删除和直接删除)...
  3. [詹兴致矩阵论习题参考解答]习题1.13
  4. 简单谈谈haskell
  5. idea 控制台程序的打包
  6. sftp方式从windows上传文件到Linux服务器
  7. 面试题(一)—Java基础(上)
  8. HM 内存池设计(2) HM6.0内存池设计
  9. 关于在群集环境下UDDI Web应用程序组件配置失败问题
  10. (转)深入理解Javascript闭包(closure)