欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2024-03-06 08:31:31
wait-notify场景 典型的wait-notify场景一般与以下两个内容相关: 1. 状态变量(state variable) 当线程需要wait的时候,总是因...

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