Java 中的 Wait 和 Notify 机制

1,716 阅读3分钟

写在前面

WaitNotifyJava 面试中常见的问题,但是在平时工作中可能不常见到。大家或多或少知道些背景知识,例如二者均为 Object 类的方法,而不是 Thread 特有的(因为是每个对象都具有的特性,因此操作锁的方法也紧跟对象,没毛病),且都只能在同步代码块中调用(即前提是先获得对象的监视器锁,一般来说在 synchronized 代码块中使用),否则抛出异常 IllegalMonitorStateException

Wait 会挂起自己让出 CPU 时间片,并将自身加入锁定对象的 Wait Set 中,释放对象的监视器锁(monitor)让其他线程可以获得,直到其他线程调用此对象的 notify( ) 方法或 notifyAll( ) 方法,自身才能被唤醒(这里有个特殊情况就是 Wait 可以增加等待时间);Notify 方法则会释放监视器锁的同时,唤醒对象 Wait Set 中等待的线程,顺序是随机的不确定。

Wait Set

虚拟机规范中定义了一个 Wait Set 的概念,但至于其具体是什么样的数据结构规范没有强制规定,意味着不同的厂商可以自行实现,但不管怎样,线程调用了某个对象的 Wait 方法,就会被加入该对象的 Wait Set

在这里插入图片描述

Demo 代码

下面通过一段 demo 来解释 WaitNotify 的功能

import java.util.concurrent.TimeUnit;

public class WaitNotify {

    public static void main(String[] args) {

        final Object A = new Object();
        final Object B = new Object();

        Thread t1 = new Thread("t1-thread") {
            @Override
            public void run() {
                synchronized (A) {
                    System.out.println(Thread.currentThread().getName() + "拿到 A 的监视器锁");
                    System.out.println(Thread.currentThread().getName() + "尝试获取 B 的监视器锁");
                    try {
                        System.out.println(Thread.currentThread().getName() + "休眠 2s,不释放 A 的监视器锁");
                        TimeUnit.SECONDS.sleep(2);
                        System.out.println(Thread.currentThread().getName() + "挂起自己,释放 A 的监视器锁");
                        A.wait();
                        System.out.println(Thread.currentThread().getName() + "被唤醒,等待获取 B 的监视器锁");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (B) {
                        System.out.println(Thread.currentThread().getName() + "拿到 B 的监视器锁");
                        B.notify();
                    }
                }
            }
        };

        Thread t2 = new Thread("t2-thread") {
            @Override
            public void run() {
                synchronized (B) {
                    System.out.println(Thread.currentThread().getName() + "拿到 B 的监视器锁");
                    System.out.println(Thread.currentThread().getName() + "尝试获取 A 的监视器锁");
                    synchronized (A) {
                        System.out.println(Thread.currentThread().getName() + "拿到 A 的监视器锁");
                        try {
                            System.out.println(Thread.currentThread().getName() + "休眠 2s,不释放 A 的监视器锁");
                            TimeUnit.SECONDS.sleep(2);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "挂起自己,释放 A 的监视器锁,唤醒 t0");
                        A.notify();
                    }
                    try {
                        System.out.println(Thread.currentThread().getName() + "休眠 2s,不释放 B 的监视器锁");
                        TimeUnit.SECONDS.sleep(2);
                        System.out.println(Thread.currentThread().getName() + "挂起自己,释放 B 的监视器锁");
                        B.wait();
                        System.out.println(Thread.currentThread().getName() + "被唤醒");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        t1.start();
        t2.start();
    }
}

输出结果

t1-thread拿到 A 的监视器锁
t2-thread拿到 B 的监视器锁
t1-thread尝试获取 B 的监视器锁
t2-thread尝试获取 A 的监视器锁
t1-thread休眠 2s,不释放 A 的监视器锁
t1-thread挂起自己,释放 A 的监视器锁
t2-thread拿到 A 的监视器锁
t2-thread休眠 2s,不释放 A 的监视器锁
t2-thread挂起自己,释放 A 的监视器锁,唤醒 t0
t2-thread休眠 2s,不释放 B 的监视器锁
t1-thread被唤醒,等待获取 B 的监视器锁
t2-thread挂起自己,释放 B 的监视器锁
t1-thread拿到 B 的监视器锁
t2-thread被唤醒

Process finished with exit code 0

时序图

在这里插入图片描述

写在最后

这是一个不定时更新的、披着程序员外衣的文青小号,欢迎关注。