Java 中的 Wait 和 Notify 機制

YoungChen發表於2019-05-11

寫在前面

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
複製程式碼

時序圖

在這裡插入圖片描述

寫在最後

這是一個不定時更新的、披著程式設計師外衣的文青小號,歡迎關注。

Java 中的 Wait 和 Notify 機制

相關文章