wait & notify 机制

前言

有时候,各个线程在执行过程需要相互通信,例如发送控制指令、向另一个暂停执行的线程发送恢复执行的指令。

如果只是发送一个控制指令,可以使用一个用 volatile 修饰的共享标记量在两个线程之间通信。如果要唤醒另一个暂停执行的线程,就涉及到了 wait/notify 机制,下面就来分析一下这个机制。

wait 和 notify 相关方法

锁本质上还是一个对象,在所有对象的父类 Object 中定义了以下几个方法:

1
2
3
4
5
6
public final void wait() throws InterruptedException
public final void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException

public final void notify();
public final void notifyAll();

这几个方法的作用如下:

方法名 说明
wait() 线程获取到锁后,调用锁对象的该方法,线程释放锁并加入到与锁对象关联的等待队列
wait(long timeout) 与 wait() 相似,不过在等待指定的毫秒数后,自动将线程移除出等待队列
wait(long timeout, int nanos) 与上面一样,只是时间粒度更小,即指定的毫秒数加上纳秒数
notify() 通知(唤醒)一个与锁对象关联的等待队列的线程,使它从 wait 方法中返回并继续往下执行
notifyAll() 与上面类似,只不过是唤醒等待队列的所有线程

使用场景

那么要在什么时候使用 wait/notify 呢?通常情况如下:

一个线程在获取到锁后,如果指定条件不满足,应该主动让出锁,并到指定区域等待,直到某个线程完成了指定条件后,再通知(唤醒)这个等待的线程,让它继续往下执行。

通用模式

了解了它的使用场景后,下面看下它是怎么用的:

通常有两个线程,一个是等待线程,另一个是通知线程

等待线程的执行步骤如下:

  1. 获取对象锁
  2. 如果指定条件不满足,就调用锁对象的 wait 方法,被唤醒后要再次检查条件,所以在判断条件时使用 while 而不是 if。
  3. 条件满足后继续往下执行

通用代码如下:

1
2
3
4
5
6
7
synchronized (对象) {
处理逻辑(可选)
while (不满足指定条件) {
对象.wait();
}
处理逻辑(可选)
}

通知线程的执行步骤如下:

  1. 获得对象锁
  2. 完成指定条件
  3. 通知(唤醒)等待队列中的线程

通用代码如下:

1
2
3
4
synchronized (对象) {
完成指定条件
对象.notifyAll();
}

注意事项

  • 必须在同步代码块中调用 wait、notify 或 notifyAll 方法,否则会抛出 IllegalMonitorStateException 异常。

因为必须要保证判断条件、调用 wait 方法,以及完成条件、调用 notify 方法都是原子性操作。不然可能会出现这样的情况:等待线程在判断完条件不满足后,还未执行 wait 方法时,突然切换到了通知线程,通知线程完成条件并调用了 notify 方法,这时再切换回等待线程,继续执行 wait 方法,这时 wait 方法就会一直等待下去。

  • 在同步代码块中,只能调用获取的锁对象的 wait、notify 或 notifyAll 方法,否则会抛出 IllegalMonitorStateException 异常。

这是因为如果当前线程不持有某个对象的锁,它就不能调用该对象的 wait 方法来让出该锁,同理,也不能调用该对象的 notify 方法来唤醒相应等待队列的线程。

  • 在调用完锁对象的 notify 或 notifyAll 方法后,等待线程并不会立即从 wait 方法返回,需要等到通知线程执行完毕并释放锁后,等待线程才能获取到锁并从 wait 方法中返回。

wait 和 sleep 的区别

  • wait 是 Object 的方法,而 sleep 是 Thread 的方法
  • 调用 wait 方法需要先获得锁,而调用 sleep 不需要
  • 调用 wait 方法后等待的线程需要 notify 来唤醒,而调用 sleep 方法后,线程会在指定等待时间后唤醒
  • 线程在调用 wait 方法后会先释放锁,而调用 sleep 方法并不会释放锁

参考

-------------    本文到此结束  感谢您的阅读    -------------
0%