Java中Object类提供了wait/notify/notifyAll方法,配合synchronized方法,可以实现等待/通知机制。本文将介绍等待/通知机制的一般模板和工作原理。
Synchronized锁的工作机制
首先来看一下Synchronized锁的工作机制。
如上图所示:
在Java中,任意一个对象都拥有自己的监视器
。当这个对象被同步块或这个对象的同步方法调用时,执行方法的线程必须先获取到这个对象的监视器 才能进入同步块或同步方法。没有获得监视器的线程将会被阻塞在同步块和同步方法的入口处(即进入同步队列),并且变为Block
状态
等待通知机制的一般模式
等待方
- 获取对象的锁
- 如果条件不满足,则调用该对象的wait方法,等待条件成熟
- 后续检查中发现条件满足,继续执行后面的逻辑
通知方
- 获取对象的锁
- 执行逻辑,改变条件
- 通知所有等待在该对象上的线程
等待方和通知方获取锁的对象应当是同一个对象,才能获取相应的通知信息。
示例代码如下:
public class Test {
final static Object object = new Object();
public static class WaitThread extends Thread {
@Override
public void run() {
synchronized (object) {
try {
System.out.println(System.currentTimeMillis() + ":waitThread wait for object!");
// wait会释放锁
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis() + ":waitThread end!");
}
}
}
public static class NotifyThread extends Thread {
@Override
public void run() {
synchronized (object) {
System.out.println(System.currentTimeMillis() + ":notifyThread run!");
object.notify();
try {
// notify之后并不会释放锁,只是通知可以将线程从等待队列迁移至同步队列
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// synchronized结束,object上的锁才释放
System.out.println(System.currentTimeMillis() + ":notifyThread end!");
}
}
}
public static void main(String[] args) {
Thread waitThread = new WaitThread();
Thread notifyThread = new NotifyThread();
waitThread.start();
notifyThread.start();
}
}
那么wait/notify和synchronized机制到底是如何协调工作的呢?
请看下图:
等待通知机制的工作原理
在上图中,笔者根据代码逻辑,将线程waitThread和notifyThread的运行流程使用数字都标识了出来。大体流程如下:
根据synchronized锁定原理,只有通过Monitor.Enter成功的对象,才能执行后续的流程。在示例代码中,waitThread先获取到了object对象的监视器,但是随后调用了wait方法,导致waitThread让出了锁,并进入了等待队列。
与此同时知notifyThread正好可以拿到object对象的监视器,从而执行自己后续的逻辑。在最终条用notify方法,“唤醒”了waitThread,从而使得waitThread从等待队列中移到了同步队列。等到notifyThread中释放了锁(Synchronized代码块结束),waitThread才能从同步队列中出队,竞争到object对象的监视器,从而继续走后续的流程。
以上就是等待/通知机制的一般流程,总结原理如下:
- 使用wait/notify/notifyAll方法时,需要先对调用对象加锁
- 调用wait方法,当前线程状态由RUNNABLE变为WAITING,并将当前线程放到对象的等待队列中
- notify/notifyAll方法会将等待队列中的一个/全部等待线程移动到同步队列中。但这些等待线程并不会立即从wait方法返回,而是需要notify/notifyAll的线程释放锁之后,才能从wait方法返回。