1. 程式人生 > 其它 >面試突擊40:執行緒休眠的方法有幾種?

面試突擊40:執行緒休眠的方法有幾種?

在 Java 中,讓執行緒休眠的方法有很多,這些方法大致可以分為兩類,一類是設定時間,在一段時間後自動喚醒,而另一個類是提供了一對休眠和喚醒的方法,線上程休眠之後,可以在任意時間對執行緒進行喚醒。

PS:休眠是指讓某個執行緒暫停執行(進入等待狀態),喚醒指的是讓某個暫停的執行緒繼續執行。

執行緒休眠的方法有以下 5 個:

  1. Thread.sleep
  2. TimeUnit
  3. wait
  4. Condition
  5. LockSupport

其中 sleep 和 TimeUnit 是讓執行緒休眠一段時間後自動喚醒,而 wait、Condition、LockSupport 提供了一對休眠和喚醒執行緒的方法,可以實現任意時刻喚醒某個執行緒。

方法1:Thread.sleep

Thread.sleep 方法來自於 Thread 類,它是一個 native 本地方法,其實現原始碼如下:

public static native void sleep(long millis) throws InterruptedException;

Thread.sleep 方法需要傳遞一個 long 型別的毫秒數,表示 n 毫秒之後自動喚醒,它的基礎用法如下:

Thread t1 = new Thread() {
    @Override
    public void run() {
        System.out.println("執行緒執行:" + LocalDateTime.now());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("執行緒結束:" + LocalDateTime.now());
    }
};
t1.start();

以上程式的執行結果如下圖所示:

方法2:TimeUnit

sleep 方法因為要傳遞一個毫秒型別的引數,因此在設定大一點的時間時比較麻煩,比如設定 1 小時或 1 天時,此時我們就可以使用 TimeUnit 來替代 sleep 方法實現休眠。
TimeUnit 的功能和 sleep 一樣,讓執行緒休眠 N 個單位時間之後自動喚醒,它的基礎用法如下:

Thread t1 = new Thread() {
    @Override
    public void run() {
        System.out.println("執行緒執行:" + LocalDateTime.now());
        try {
            TimeUnit.SECONDS.sleep(1); // 休眠 1s
            //TimeUnit.DAYS.sleep(1); // 休眠 1 天
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("執行緒結束:" + LocalDateTime.now());
    }
};
t1.start();

以上程式的執行結果如下圖所示:

當我們檢視 TimeUnit 原始碼時就會發現,它的底層是基於 Thread.sleep 方法實現的,其實現原始碼如下:

方法3:wait

wait/notify/notifyAll 都來自於 Object 類,其中:

  • wait() / wait(long timeout):表示讓當前執行緒進入休眠狀態。
  • notify():喚醒當前物件上的一個休眠執行緒。
  • notifyAll():喚醒當前物件上的所有休眠執行緒。

其中 wait() 方法表示讓當前執行緒無限期等待下去,直到遇到 notify/notifyAll 方法時才會被喚醒,而 wait(long timeout) 表示接收一個 long 型別的超時時間,如果沒有遇到 notify/notifyAll 會在 long 毫秒之後自動喚醒,如果遇到了 notify/notifyAll 方法會立即被喚醒。
它的基礎用法如下:

Object lock = new Object();
new Thread(() -> {
    synchronized (lock) {
        try {
            // 讓當前執行緒休眠
            lock.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}).start();
synchronized (lock) {
    lock.notify(); // 喚醒當前物件上一個休眠執行緒
    // lock.notifyAll(); // 喚醒當前物件上所有休眠的執行緒
}

需要注意的是 wait/notify/notifyAll 在使用時必須要配合 synchronized 一起使用,否則程式執行會報錯。

方法4:Condition

Condition 作為 wait 的升級版,它提供的常用方法有以下幾個:

  • await():讓當前執行緒進入等待狀態,直到被通知(signal)或者被中斷時才會繼續執行。
  • awaitUninterruptibly():讓當前執行緒進入等待狀態,直到被通知才會被喚醒,它對執行緒的中斷通知不做響應。
  • await(long time, TimeUnit unit):在 await() 方法的基礎上添加了超時時間,如果過了超時時間還沒有遇到喚醒方法則會自動喚醒並恢復執行。
  • awaitUntil(Date deadline):讓當前執行緒進入等待狀態,如果沒有遇到喚醒方法也會在設定的時間之後自動喚醒。
  • signal():喚醒一個等待在 Condition 上的執行緒。
  • signalAll():喚醒等待在 Condition 上所有的執行緒。

它的基本用法如下:

import java.time.LocalDateTime;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionExample {
    public static void main(String[] args) throws InterruptedException {
        // 建立鎖
        final Lock lock = new ReentrantLock();
        // 建立 Condition
        final Condition condition = lock.newCondition();
        new Thread(() -> {
            System.out.println("執行緒執行:" + LocalDateTime.now());
            lock.lock(); // 得到鎖
            try {
                // 休眠執行緒
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock(); // 釋放鎖
            }
            System.out.println("執行緒結束:" + LocalDateTime.now());
        }).start();
        Thread.sleep(1000);
        lock.lock(); // 得到鎖
        try {
            // 喚醒執行緒
            condition.signal();
        } finally {
            lock.unlock(); // 釋放鎖
        }
    }
}

相比於 wait 方法,Condition 物件更加靈活,因為它可以在一把鎖上定義多個 Condition 物件進行使用,如下程式碼所示:

 // 建立鎖
final Lock lock = new ReentrantLock();
// 建立 Condition 1
final Condition condition = lock.newCondition();
// 建立 Condition 2
final Condition condition2 = lock.newCondition();
// ......

方法5:LockSupport

LockSupport 是更加底層的操作執行緒休眠和喚醒的物件,它提供了兩個常用的方法:

  • LockSupport.park():休眠當前執行緒。
  • LockSupport.unpark(Thread thread):喚醒一個指定的執行緒。

它的基礎用法如下:

Thread t1 = new Thread(() -> {
    System.out.println("執行緒1休眠");
    LockSupport.park(); // 休眠執行緒
    System.out.println("執行緒1執行結束");
}, "執行緒1");
t1.start();

Thread t2 = new Thread(() -> {
    System.out.println("執行緒2休眠");
    LockSupport.park(); // 休眠執行緒
    System.out.println("執行緒2執行結束");
}, "執行緒2");
t2.start();

Thread t3 = new Thread(() -> {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("喚醒執行緒1");
    LockSupport.unpark(t1); // 喚醒執行緒1
}, "執行緒3");
t3.start();

以上程式的執行結果如下圖所示:

總結

Thread.sleep 和 TimeUnit 是讓執行緒休眠並在一段時間後自動喚醒,而 wait、Condition、LockSupport 提供了休眠和喚醒執行緒的方法,其中 Condition 為 wait 方法的升級版,而 LockSupport 是更底層的讓執行緒休眠和喚醒的方法,它可以實現喚醒某個指定的執行緒,這是其它方法所不具備的(功能)。

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

公眾號:Java面試真題解析

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