0 前言
上一节讲了 Synchronized 关键词的原理与优化分析, 而配合 Synchronized 使用的另外两个关键词 wait¬ify 是本章讲解的重点. 最简单的东西, 往往包含了最复杂的实现, 因为需要为上层的存在提供一个稳定的基础, Object 作为 Java 中所有对象的基类, 其存在的价值不言而喻, 其中 wait¬ify 方法的实现多线程协作提供了保证.
1 源码
今天我们要学习或者说分析的是 Object 类中的 wait¬ify 这两个方法, 其实说是两个方法, 这两个方法包括他们的重载方法一共有 5 个, 而 Object 类中一共才 12 个方法, 可见这 2 个方法的重要性. 我们先看看 JDK 中的代码:
- public final native void notify();
- public final native void notifyAll();
- public final void wait() throws InterruptedException { wait(0);
- }
- public final native void wait(long timeout) throws InterruptedException;
- public final void wait(long timeout, int nanos) throws InterruptedException {
- if (timeout <0) {
- throw new IllegalArgumentException("timeout value is negative");
- }
- if (nanos < 0 || nanos> 999999) {
- throw new IllegalArgumentException(
- "nanosecond timeout value out of range");
- }
- // 此处对于纳秒的处理不精准, 只是简单增加了 1 毫秒,
- if (nanos> 0) {
- timeout++;
- }
- wait(timeout);
- }
复制代码
就是这五个方法. 其中有 3 个方法是 native 的, 也就是由虚拟机本地的 c 代码执行的. 有 2 个 wait 重载方法最终还是调用了 wait(long) 方法.
wait 方法: wait 是要释放对象锁, 进入等待池. 既然是释放对象锁, 那么肯定是先要获得锁. 所以 wait 必须要写在 synchronized 代码块中, 否则会报异常.
notify 方法: 也需要写在 synchronized 代码块中, 调用对象的这两个方法也需要先获得该对象的锁. notify,notifyAll, 唤醒等待该对象同步锁的线程. notify 唤醒对象等待池中的一个线程, 将这个线程放入该对象的锁池中. 对象的锁池中线程可以去竞争得到对象锁, 然后开始执行.
如果是通过 notify 来唤起的线程, 那先进入 wait 的线程会先被唤起来, 并非随机唤醒;
如果是通过 nootifyAll 唤起的线程, 默认情况是最后进入的会先被唤起来, 即 LIFO 的策略;
另一点, notify,notifyAll 调用时并不会释放对象锁. 比如以下代码:
- public void test()
- {
- Object object = new Object();
- synchronized (object){
- object.notifyAll();
- while (true){
- }
- }
- }
复制代码
虽然调用了 notifyAll, 但是紧接着进入了一个死循环. 导致一直不能出临界区, 一直不能释放对象锁. 所以, 即使它把所有在等待池中的线程都唤醒放到了对象的锁池中, 但是锁池中的所有线程都不会运行, 因为他们都拿不到锁.
2 用法
简单示例:
- public class WaitNotifyCase {
- public static void main(String[] args) {
- final Object lock = new Object();
- new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println("thread A is waiting to get lock");
- synchronized (lock) {
- try {
- System.out.println("thread A get lock");
- TimeUnit.SECONDS.sleep(1);
- System.out.println("thread A do wait method");
- lock.wait();
- System.out.println("wait end");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }).start();
- new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println("thread B is waiting to get lock");
- synchronized (lock) {
- System.out.println("thread B get lock");
- try {
- TimeUnit.SECONDS.sleep(5);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- lock.notify();
- System.out.println("thread B do notify method");
- }
- }
- }).start();
- }
- }
复制代码
执行结果:
- thread A is waiting to get lock
- thread A get lock
- thread B is waiting to get lock
- thread A do wait method
- thread B get lock
- thread B do notify method
- wait end
复制代码
前提: 必须由同一个 lock 对象调用 wait,notify 方法
当线程 A 执行 wait 方法时, 该线程会被挂起;
当线程 B 执行 notify 方法时, 会唤醒一个被挂起的线程 A;
lock 对象, 线程 A 和线程 B 三者是一种什么关系? 根据上面的结论, 可以想象一个场景:
lock 对象维护了一个等待队列 list;
线程 A 中执行 lock 的 wait 方法, 把线程 A 保存到 list 中;
线程 B 中执行 lock 的 notify 方法, 从等待队列中取出线程 A 继续执行;
来源: https://juejin.im/post/5b74f4d86fb9a009764bbf73