详解Java程序并发的Wait-Notify机制

 更新时间:2015年07月30日 10:55:02   作者:低调小一  
这篇文章主要介绍了详解Java程序并发的Wait-Notify机制,多线程并发是Java编程中的重要部分,需要的朋友可以参考下

Wait-Notify场景
典型的Wait-Notify场景一般与以下两个内容相关:
1. 状态变量(State Variable)
当线程需要wait的时候,总是因为一些条件得不到满足导致的。例如往队列里填充数据,当队列元素已经满时,线程就需要wait停止运行。当队列元素有空缺时,再继续自己的执行。
2. 条件断言(Condition Predicate)
当线程确定是否进入wait或者是从notify醒来的时候是否继续往下执行,大部分都要测试状态条件是否满足。例如,往队列里添加元素,队列已满,于是阻塞当前线程,当有其他线程从队列里取走了元素,就通知在等待的线程“队列有剩余空间,可以往里添加元素了”。这时,等待添加元素的进程就会被唤醒,然后判断一下当前队列是否真的有剩余空间,如果真的有剩余空间,就将元素添加进去,如果没有,则继续阻塞等待下次唤醒。
3. 条件队列(Condition Queue)
每个对象都有一个内置的条件队列,当一个线程在该对象锁上调用wait函数的时候,就会将该线程加入到该对象的条件队列中。

注意
wait与notify是Java同步机制中的重要组成部分。结合与synchronized关键字使用,可以建立很多优秀的同步模型,例如生产者-消费者模型。但是在使用wait()、notify()、notifyAll()函数的时候,需要特别注意以下几点:

    wait()、notify()、notifyAll()方法不属于Thread类,而是属于Object基础类,也就是说每个对象都有wait()、notify()、notifyAll()的功能。因为每个对象都有锁,锁是每个对象的基础,因此操作锁的方法也是最基础的。
    调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj){...} 代码段内。
    调用obj.wait()后,线程A就释放了obj的锁,否则线程B无法获得obj锁,也就无法在synchronized(obj){...} 代码段内唤醒线程A。
    当obj.wait()方法返回后,线程A需要再次获得obj锁,才能继续执行。
    如果线程A1,A2,A3都在obj.wait(),则线程B调用obj.notify()只能唤醒线程A1,A2,A3中的一个(具体哪一个由JVM决定)。
    如果线程B调用obj.notifyAll()则能全部唤醒等待的线程A1,A2,A3,但是等待的线程要继续执行obj.wait()的下一条语句,必须获得obj锁。因此,线程A1,A2,A3只有一个有机会获得锁继续执行,例如A1,其余的需要等待A1释放obj锁之后才能继续执行。
    当线程B调用obj.notify()或者obj.notifyAll()的时候,线程B正持有obj锁,因此,线程A1,A2,A3虽被唤醒,但是仍无法获得obj锁。直到线程B退出synchronized代码块,释放obj锁后,线程A1,A2,A3中的一个才有机会获得对象锁并得以继续执行。


示例代码
线程的wait操作的典型代码结构如下:

  public void test() throws InterruptedException { 
    synchronized(obj) { 
      while (! contidition) { 
        obj.wait(); 
      } 
    } 
  } 

为什么obj.wait()操作必须位于循环中呢?有以下几个主要原因:
1. 一个对象锁可能用于保护多个状态变量,当它们都需要wait-notify操作时,如果不将wait放到while循环中就会有问题。例如,某对象锁obj保护两种状态变量a和b,当a的条件断言不成立时发生了wait操作,当b的条件断言不成立时也发生了wait操作,两个线程被加入到obj对应的条件队列中。现在若改变状态变量a的某操作发生,在obj上调用了notifyAll操作,则obj对应的条件队列里的所有线程均被唤醒,之前等待a的一个或几个线程去判断a的条件断言可能成立了,但是b对于的条件断言肯定仍不成立,而此时等待b的线程也被唤醒了,所以需要循环判断b的条件断言是否满足,如果不满足,则继续wait。
2. 多个线程wait的同一状态的条件断言。例如,向队列添加元素的场景,当前队列是满的,多个线程想往里面添加元素,于是都wait了。此时,另一个线程从队列里取出了一个元素,调用了notifyAll操作,唤醒了所有线程,但是只有一个线程能够往队列里添加一个元素,其他的仍需要等待。
3. 虚假唤醒。在没有被通知、中断或超时的情况下,线程自动苏醒了。虽然这种情况在实践中很少发生,但是通过循环等待可以杜绝这一情况的发生。


相关文章

  • Java如何导入Jsoup库做一个有趣的爬虫项目

    Java如何导入Jsoup库做一个有趣的爬虫项目

    Jsoup库是一款Java的HTML解析器,可用于从网络或本地文件中获取HTML文档并解析其中的数据,这篇文章给大家介绍Java导入Jsoup库做一个有趣的爬虫项目,感兴趣的朋友跟随小编一起看看吧
    2023-11-11
  • 解决spring boot环境切换失效的问题

    解决spring boot环境切换失效的问题

    这篇文章主要介绍了解决spring boot环境切换失效的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • 利用Postman和Chrome的开发者功能探究项目(毕业设计项目)

    利用Postman和Chrome的开发者功能探究项目(毕业设计项目)

    这篇文章主要介绍了利用Postman和Chrome的开发者功能探究项目(毕业设计项目),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • Java 并发编程之ForkJoin框架

    Java 并发编程之ForkJoin框架

    这篇文章主要为大家介绍了Java ForkJoin框架,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助,希望能够给你带来帮助
    2021-11-11
  • Java系统运行缓慢等问题的排查思路

    Java系统运行缓慢等问题的排查思路

    这篇文章主要介绍了Java系统运行缓慢等问题的排查思路,读者可以根据具体情况具体分析,从而解决问题
    2021-04-04
  • 基于java的opencv开发过程详解

    基于java的opencv开发过程详解

    这篇文章主要介绍了基于java的opencv开发过程详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • SpringBoot ThreadLocal实现公共字段自动填充案例讲解

    SpringBoot ThreadLocal实现公共字段自动填充案例讲解

    每一次在Controller层中封装改动数据的方法时都要重新设置一些共性字段,显得十分冗余。为了解决此问题也是在项目中第一次利用到线程,总的来说还是让我眼前一亮,也开阔了视野,对以后的开发具有深远的意义
    2022-10-10
  • java后台实现支付宝支付接口和支付宝订单查询接口(前端为APP)

    java后台实现支付宝支付接口和支付宝订单查询接口(前端为APP)

    这篇文章主要介绍了java后台实现支付宝支付接口和支付宝订单查询接口(前端为APP),非常具有实用价值,需要的朋友可以参考下
    2018-08-08
  • 在编码时如何使用\r与\n,两者的区别

    在编码时如何使用\r与\n,两者的区别

    本篇文章是对\r与\n两者的区别,以及编程语言中如何使用进行了详细的分析介绍,需要的朋友可以参考下
    2015-07-07
  • 详解java动态代理模式

    详解java动态代理模式

    这篇文章主要为大家详细介绍了java动态代理模式,总结一下代理模式,以及jdk,cglib代理模式用法,来理解代理模式,感兴趣的小伙伴们可以参考一下
    2016-02-02

最新评论