java 中的wait & notify

Wait&Notify

以下内容来自《Java并发编程的艺术》,4.3.2 等待/通知机制

线程A等待某一个变量_v_满足某个条件,而线程B会在某个不确定的时刻修改_v_,以使其满足条件,那么线程A所要做的无非以下两种操作:

  • 轮询变量_v_,直到_v_满足条件,A继续完成它的工作
  • 每隔一段时间检查变量_v_,这期间可能休眠,也可能做其它的事

可以看到,以上两种操作刚好是矛盾的,第一种会一直占用CPU资源,而且是在浪费,但是可以保证实时性,即当_v_满足条件,它立刻就会知道。第二种会降低CPU的开销,或者减少浪费,但是很难保证实时性。

Java内置的等待/通知机制能够很好的解决这个矛盾并实现所需的功能。

等待/通知相关的方法是任意Java对象都具有的,这些方法定义在java.lang.Object

Method Desc
notify()
notifyAll()
wait()
wait(long)
wait(long, int)

JDK中关于这几个方法的解释:

public final native void notify()

唤醒一个在该对象监视器上等待的线程。如果有很多线程在该对象上等待,就挑选其中的一个。选择是任意的,在具体实现的时候自由裁决。一个线程通过调用该对象的wait方法,开始在该对象的监视器锁上等待(前提是该线程持有该监视器锁)。

被唤醒的线程并不是立即开始执行,要等到当前线程释放掉该对象锁(或从同步块中出来)。被唤醒的线程将以常规的方式继续在该对象同步块中完成它的工作。例如,唤醒的线程对于接下来要获得锁的线程没有优先级。

该方法应该被持有该对象监视器的线程调用。有三种方式使一个线程拥有对象监视器:

  • 执行该对象的同步方法
  • 进入同步代码块
  • 对于类对象,执行类的静态同步方法

同一时刻只有一个线程能持有对象监视器。

Throws:IllegalMonitorStateException 如果当前线程不持有该对象的监视器

public final native void notifyAll()

唤醒所有在该对象锁上等待的线程。即所有线程都开始准备进入该对象监视器。

public final native void wait(long timeout) throws InterruptedException

引起当前线程等待,直到其它线程调用该对象的notify()notifyAll()方法,或者在特定的超时时间之后。

当前线程需要持有该对象的监视器。

该方法会引起当前线程(如线程T),使其放弃持有的对象锁,将自身放入该对象的等待集合。线程T不能被线程调度器调度,会保持挂起直到如下发生:

  • 某个其它线程调用该对象的notify()方法,线程T可能会被随机选中,并唤醒
  • 其它线程调用该对象的notifyAll()方法
  • 其它线程中断线程T
  • 指定的超时时间到了。如果超时时间设为0,那么超时时间无效,结果等价于wait()方法

然后,线程T会从该对象的等待集合中移除,然后再次可被调度。然后它会再次与其它线程竞争进入对象监视器的机会,一旦它取得了控制权,它再次回到它睡眠前的状态,就好像没有发上一样,继续执行。

线程可以不需要通知,中断或超时而被唤醒,这里叫做伪唤醒。尽管实际很少发生,程序必须小心测试唤醒线程的条件,而且当条件不满足的时候,换句话说,应该让wait()方法在一个循环中执行,直到条件满足,就像这样:

synchronized(obj){
  while(<condition is not satisfied>){
    obj.wait();
  }
  //do bussiness
}

如果当前线程在等待之前,或正在等待的时候被中断了,就抛出InterruptedException异常。这个异常会知道线程再次获得锁的时候才抛出。

当该线程在等待的时候,它还是有可能持有其它的对象锁!这里一定要注意。

Throws#####

IllegalArgumentException:timeout参数为负值

IllegalMonitorStateException:如果当前线程没有持有该对象锁

InterruptedException:如果其它线程在该线程开始等待或正在等待的时候中断线程,中断标志位会清除。

来个图:

Wait&Notify

总结如下过程:

time:01			WaitThread							NotifyThread
time:02			进入监视器							...
time:03			...									进入监视器
time:04			...									...	
time:05			进入成功							 ...
time:06			...									进入失败
time:07			...									...
time:08			...									进入同步队列
time:09			Object.wait()						 ...
time:10			...									从同步队列出来
time:11			进入等待队列						    进入监视器
time:12			...									...
time:13			...									进入成功
time:14			...									...
time:15			...									Object.notify
time:16			...									...
time:17			从等待队列进入同步队列				     ...
time:18			...									出监视器
time:19			进入监视器							...
time:20			...									结束
time:21			进入成功
time:22			...
time:23			...
time:24			结束

过程就是这么个过程,纵向为时间。

等待/通知最佳实践

等待方遵循原则:

  • 获取对象锁
  • 如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件
  • 条件满足就执行对应的逻辑

对应的伪代码:

synchronized(obj){
  while(<条件不满足>){
    obj.wait();
  }
  //执行相应逻辑
}

通知方伪代码:

  • 获得对象锁
  • 改变条件
  • 通知所有在该对象的线程(可能会通知到业务不相关的线程)

对应伪代码:

sychronized(obj){
  //改变条件
  obj.notifyAll();
}
posted @ 2017-01-31 00:09  JintaoXIAO  阅读(363)  评论(0编辑  收藏  举报