切換語言為:簡體

在 Java 中,執行緒等待和喚醒的多種實現方式以及相互之間的區別

  • 爱糖宝
  • 2024-07-31
  • 2068
  • 0
  • 0

在 Java 中,執行緒等待和喚醒的實現手段有以下幾種方式:

  1. Object 類下的 wait()、notify() 和 notifyAll() 方法;

  2. Condition 類下的 await()、signal() 和 signalAll() 方法;

  3. LockSupport 類下的 park() 和 unpark() 方法。

為什麼一個執行緒等待和喚醒機制就需要這麼多的實現方式呢?彆着急,咱們先來看實現,再來說原因。

# 一、wait/notify/notifyAll

Object 類的方法說明:

  1. wait():讓當前執行緒處於等待狀態,並釋放當前擁有的鎖;

  2. notify():隨機喚醒等待該鎖的其他執行緒,重新獲取鎖,並執行後續的流程,只能喚醒一個執行緒;

  3. notifyAll():喚醒所有等待該鎖的執行緒(鎖只有一把,雖然所有執行緒被喚醒,但所有執行緒需要排隊執行)。

示例程式碼如下:

Object lock = new Object();// 建立執行緒並執行new Thread(() -> {
    System.out.println("執行緒1:開始執行");
    synchronized (lock) {
        try {
            System.out.println("執行緒1:進入等待");
            lock.wait();
            System.out.println("執行緒1:繼續執行");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("執行緒1:執行完成");
    }}).start();Thread.sleep(1000);synchronized (lock) {
    // 喚醒執行緒
    System.out.println("執行 notifyAll()");
    lock.notifyAll();
    }

# 二、await/signal/signalAll

Condition 類的方法說明:

  1. await():對應 Object 的 wait() 方法,執行緒等待;

  2. signal():對應 Object 的 notify() 方法,隨機喚醒一個執行緒;

  3. signalAll():對應 Object 的 notifyAll() 方法,喚醒所有執行緒。

示例程式碼如下:

// 建立 Condition 物件
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition(); // lock 下可建立多個 Condition
// 加鎖
lock.lock();
try {
    // 業務方法......
    // 1.進入等待狀態
    condition.await();
    // 2.喚醒操作
    condition.signal();
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    lock.unlock();
}

# 三、park/unpark

LockSupport 類的方法說明:

  1. LockSupport.park():休眠當前執行緒。

  2. LockSupport.unpark(執行緒物件):喚醒某一個指定的執行緒。

PS:LockSupport 無需配鎖(synchronized 或 Lock)一起使用。

示例程式碼如下:

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        LockSupport.park();
        System.out.println("執行緒1");
    }, "執行緒1");
    t1.start();
    Thread t2 = new Thread(() -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("喚醒執行緒1");
        LockSupport.unpark(t1);
    }, "執行緒2");
    t2.start();
}

# 四、小結

為什麼一個執行緒等待和喚醒的功能需要這麼多的實現呢?

  1. LockSupport 存在的必要性:前兩種方法 notify 方法以及 signal 方法都是隨機喚醒,如果存在多個等待執行緒的話,可能會喚醒不應該喚醒的執行緒,因此有 LockSupport 類下的 park 和 unpark 方法指定喚醒執行緒是非常有必要的。

  2. Condition 存在的必要性:Condition 相比於 Object 類的 wait 和 notify/notifyAll 方法,前者可以建立多個等待集,例如,我們可以建立一個生產者等待喚醒物件,和一個消費者等待喚醒物件,這樣我們就能實現生產者只能喚醒消費者,而消費者只能喚醒生產者的業務邏輯了,如下程式碼所示:

// 建立 Condition 物件
private Lock lock = new ReentrantLock();
// 生產者的 Condition 物件
private Condition producerCondition = lock.newCondition();
// 消費者的 Condition 物件
private Condition consumerCondition = lock.newCondition();

也就是 Condition 是 Object 等待喚醒模型的升級,Object 類可以實現的功能它都能實現,但 Condition 能實現的功能,Object 卻不能實現,這就是 Condition 類存在的必要性。

那問題來了,為什麼還有會 Object 的 wait 和 notify 方法呢? 因為 Object 類誕生的比較早,也就是說 Condition 和 LockSupport 都是 JDK 後期版本纔出現的功能,所以就有了現在這麼多執行緒喚醒和等待的方法了。

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.