面試突擊41:notify是隨機喚醒嗎?

  • 2022 年 4 月 20 日
  • 筆記

做 Java 開發的小夥伴,對 wait 方法和 notify 方法應該都比較熟悉,這兩個方法在執行緒通訊中使用的頻率非常高,但對於 notify 方法的喚醒順序,有很多小夥伴的理解都是錯誤的,有很多人會認為 notify 是隨機喚醒的,但它真的是隨機喚醒的嗎?

帶著這個疑問,我們嘗試休眠 100 個執行緒,再喚醒 100 個執行緒,並把執行緒休眠和喚醒的順序保持到兩個集合中,最後再列印一下這兩個集合,看一下它們的執行順序,如果它們的順序是一致的,那說明 notify 是順序喚醒的,否則則是隨機喚醒的,notify 測試程式碼如下:

import java.util.ArrayList;
import java.util.List;

public class NotifyExample {
    // 保存休眠執行緒的順序
    private static List<String> waitList = new ArrayList<>();
    // 保存喚醒執行緒的順序
    private static List<String> notifyList = new ArrayList<>();

    public static void main(String[] args) throws InterruptedException {
        final Object lock = new Object();
        // 休眠 100 個執行緒
        for (int i = 0; i < 100; i++) {
            String threadName = Integer.toString(i); // 定義執行緒名
            new Thread(() -> {
                // 獲取當前執行執行緒的執行緒名
                String currThreadName = Thread.currentThread().getName();
                synchronized (lock) {
                    waitList.add(currThreadName); // 存入等待 list
                    try {
                        lock.wait(); // 休眠執行緒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    notifyList.add(currThreadName); // 存儲喚醒 list
                }
            }, threadName).start();
        }
        Thread.sleep(1000);
        // 喚醒 100 個執行緒
        for (int i = 0; i < 100; i++) {
            synchronized (lock) {
                lock.notify(); // 喚醒執行緒
            }
        }
        // 列印 2 個執行緒列表
        System.out.println("等待執行緒順序:" + waitList);
        System.out.println("喚醒執行緒順序:" + waitList);
    }
}

以上程式的執行結果如下圖所示:
image.png
從上述列印的結果我們可以看出,使用 notify 並不是隨機喚醒的,而是順序喚醒的,雖然以上程式碼能證明這個結論,但為了更清楚的解釋這個問題,我們查看了 notify 的實現源碼,它的源碼內容如下:
image.png
簡單翻譯一下上面的重點內容,notify 選擇喚醒的執行緒是任意的,但具體的實現還要依賴於 JVM。也就是說 notify 的喚醒規則,最終取決於 JVM 廠商,不同的廠商的實現可能是不同的,比如阿里的 JVM 和 Oracle 的 JVM,關於 notify 的喚醒規則可能是不一樣的。

那作為一個普通的程式設計師我們要研究的就是官方的 JVM 也就是 HotSpot 虛擬機,它的 notify 實現源碼在 ObjectMonitor.cpp 中,具體源碼如下:
image.png
DequeueWaiter 方法實現的源碼如下:
image.png
從上述源碼可以看出,在進行喚醒時,每次會從 _WaitSet 等待集合中獲取第一個元素進行出隊操作,這也說明了 notify 是順序喚醒的。

總結

notify 喚醒執行緒的規則是隨機喚醒還是順序喚醒取決於 JVM 的具體實現,作為主流的 HotSpot 虛擬機中的 notify 的喚醒規則是順序的,也就是 notify 會按照執行緒的休眠順序,依次喚醒執行緒。

是非審之於己,毀譽聽之於人,得失安之於數。

公眾號:Java面試真題解析

面試合集://gitee.com/mydb/interview