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

所属分类: 软件编程 / java 阅读数: 40
收藏 0 赞 0 分享

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. 虚假唤醒。在没有被通知、中断或超时的情况下,线程自动苏醒了。虽然这种情况在实践中很少发生,但是通过循环等待可以杜绝这一情况的发生。


更多精彩内容其他人还在看

SpringBoot中使用Ehcache的详细教程

EhCache 是一个纯 Java 的进程内缓存框架,具有快速、精干等特点,是 Hibernate 中默认的 CacheProvider。这篇文章主要介绍了SpringBoot中使用Ehcache的相关知识,需要的朋友可以参考下
收藏 0 赞 0 分享

在idea 中添加和删除模块Module操作

这篇文章主要介绍了在idea 中添加和删除模块Module操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
收藏 0 赞 0 分享

java spring整合junit操作(有详细的分析过程)

这篇文章主要介绍了java spring整合junit操作(有详细的分析过程),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
收藏 0 赞 0 分享

详解JAVA 弱引用

这篇文章主要介绍了 JAVA 弱引用的相关资料,帮助大家更好的理解和学习java引用对象,感兴趣的朋友可以了解下
收藏 0 赞 0 分享

深入了解JAVA 虚引用

这篇文章主要介绍了JAVA 虚引用的相关资料,帮助大家更好的理解和学习JAVA,感兴趣的朋友可以了解下
收藏 0 赞 0 分享

详解JAVA 强引用

这篇文章主要介绍了JAVA 强引用的相关资料,帮助大家更好的理解和学习,感兴趣的朋友可以了解下
收藏 0 赞 0 分享

java中的按位与(&)用法说明

这篇文章主要介绍了java中的按位与(&)用法说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
收藏 0 赞 0 分享

深入了解JAVA 软引用

这篇文章主要介绍了JAVA 软引用的相关资料,帮助大家更好的理解和学习,感兴趣的朋友可以了解下
收藏 0 赞 0 分享

利用MyBatis实现条件查询的方法汇总

这篇文章主要给大家介绍了关于利用MyBatis实现条件查询的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者使用MyBatis具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
收藏 0 赞 0 分享

Intellij IDEA 与maven 版本不符 Unable to import maven project See logs for details: No implementation for org.apache.maven.model.path.PathTranslator was bound

这篇文章主要介绍了Intellij IDEA 与maven 版本不符 Unable to import maven project See logs for details: No implementation for org.apache.maven.model.path.Pa
收藏 0 赞 0 分享
查看更多