一張圖搞清楚wait、sleep、join、yield四者區別,面試官直接被征服!

JavaBuild發表於2024-03-14

寫在開頭

線上程的生命週期中,不同狀態之間切換時,可以透過呼叫sleep()、wait()、join()、yield()等方法進行執行緒狀態控制,針對這一部分知識點,面試官們也會做做文章,比如問你這些方法的作用以及之間的區別。

那麼今天我們就一起來總結一下這幾個方法的作用及區別,先畫一個思維導圖梳理一下,便於理解與記憶,爭取在被問到這個點時徹底征服面試官!(圖片可儲存常看哈
image

sleep()

sleep()是Thread類中的一個靜態本地方法,透過設定方法中的時間引數,使呼叫它的執行緒休眠指定時間,執行緒從RUNNING狀態轉為BLOCKED狀態,這個過程中會釋放CPU資源,給其他執行緒執行機會時不考慮執行緒的優先順序,但如果有同步鎖則sleep不會釋放鎖即其他執行緒無法獲得同步鎖,需要注意的是sleep()使用時要處理異常。休眠時間未到時,可透過呼叫interrupt()方法來喚醒休眠執行緒。

【程式碼示例1】

try {//sleep會發生異常要顯示處理
    Thread.sleep(20);//暫停20毫秒
} catch (InterruptedException e) {
    e.printStackTrace();
}

為什麼sleep()放在Thread類中

答:因為sleep是執行緒級別的休眠,不涉及到物件類,只是讓當前執行緒暫停,進入休眠狀態,並不釋放同步鎖資源,也不需要去獲得物件鎖。

wait()

wait() 是Object類的成員本地方法,會讓持有物件鎖的執行緒釋放鎖,進入執行緒等待池中等待被再次喚醒(notify隨機喚醒,notifyAll全部喚醒,執行緒結束自動喚醒)即放入鎖池中競爭同步鎖,同時釋放CPU資源,它的呼叫必須在同步方法或同步程式碼塊中執行,也需要捕獲 InterruptedException 異常。

【程式碼示例2】

//同步程式碼塊
 synchronized (obj) {
     System.out.println("obj to wait on RunnableImpl1");
     try {
         obj.wait();
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
     System.out.println("obj continue to run on RunnableImpl1");
 }

為什麼wait()是Object的方法

答:每個物件都擁有物件鎖,wait的作用是釋放當前執行緒所佔有的物件鎖,自然是要操作對應的Object而不是Thread,因此wait要放入到Object中。

join()

join()同樣是Thread中的一個方法,呼叫join的執行緒擁有優先使用CPU時間片的權利,其他執行緒需要等待join()呼叫執行緒執行結束後才能繼續執行,探索其底層會發現,它的底層是透過wait()進行實現,因此它也需要處理異常。

【程式碼示例3】

//建立TestRunnable類
TestRunnable mr = new TestRunnable();
//建立Thread類的有參構造,並設定執行緒名
Thread t1 = new Thread(mr, "t1");
Thread t2 = new Thread(mr, "t2");
Thread t3 = new Thread(mr, "t3");
//啟動執行緒
t1.start();
try {
    t1.join(); //等待t1執行完才會輪到t2,t3搶
} catch (InterruptedException e) {
    e.printStackTrace();
}
t2.start();
t3.start();

我們跟進join()方法內部會發現,其底層主要透過wait()實現,其中引數代表等待當前執行緒最多執行 millis 毫秒,如果 millis 為 0,則會一直執行,直至完成,其他執行緒才會繼續向下;

【原始碼示例1】

    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

yield()

yield()是Thread的一個靜態方法,它的呼叫不需要傳入時間引數,並且yield() 方法只會給相同優先順序或更高優先順序的執行緒執行的機會,並且呼叫yield的執行緒狀態會轉為就緒狀態,呼叫yield方法只是一個建議,告訴執行緒排程器我的工作已經做的差不多了,可以讓別的執行緒使用CPU了,沒有任何機制保證採納。所以可能它剛讓出CPU時間片,又被執行緒排程器分配了一個時間片繼續執行了。使用時不需要處理異常。

【程式碼示例4】

public class Test {

    public static void main(String[] args) {
        Thread thread1 = new Thread(Test::printNumbers, "小明");
        Thread thread2 = new Thread(Test::printNumbers, "小華");
        thread1.start();
        thread2.start();
    }
    private static void printNumbers() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);

            // 當 i 是偶數時,當前執行緒暫停執行
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + " 讓出控制權...");
                Thread.yield();
            }
        }
    }
}

輸出:

小明: 1
小華: 1
小華: 2
小明: 2
小明 讓出控制權...
小華 讓出控制權...
小明: 3
小明: 4
小明 讓出控制權...
小明: 5
小華: 3
小華: 4
小華 讓出控制權...
小華: 5

總結

上文中,我們結合程式碼示例,對這個四個方法進行了詳細介紹,下面我們以sleep()為參照,進行對比總結:

(1)sleep()與wait()的區別?

  1. sleep() 是 Thread 類的靜態本地方法;wait() 是Object類的成員本地方法;
  2. JDK1.8 sleep() wait() 均需要捕獲 InterruptedException 異常;
  3. sleep() 方法可以在任何地方使用;wait() 方法則只能在同步方法或同步程式碼塊中使用;
  4. sleep() 會休眠當前執行緒指定時間,釋放 CPU 資源,不釋放物件鎖,休眠時間到自動甦醒繼續執行;wait() 方法放棄持有的物件鎖,進入等待佇列,當該物件被呼叫 notify() / notifyAll() 方法後才有機會競爭獲取物件鎖,進入執行狀態。

(2)sleep()與yield()的區別?

  1. sleep() 方法給其他執行緒執行機會時不考慮執行緒的優先順序;yield() 方法只會給相同優先順序或更高優先順序的執行緒執行的機會;
  2. sleep() 方法宣告丟擲 InterruptedException;yield() 方法沒有宣告丟擲異常;
  3. 執行緒執行 sleep() 方法後進入超時等待狀態;執行緒執行 yield() 方法轉入就緒狀態,可能馬上又得得到執行;
  4. sleep() 方法需要指定時間引數;yield() 方法出讓 CPU 的執行權時間由 JVM 控制。

(3)sleep()與join()的區別?

  1. JDK1.8 sleep() join() 均需要捕獲 InterruptedException 異常;
  2. sleep()是Thread的靜態本地方法,join()是Thread的普通方法;
  3. sleep()不會釋放鎖資源,join()底層是wait方法,會釋放鎖。

結尾彩蛋

如果本篇部落格對您有一定的幫助,大家記得留言+點贊+收藏呀。原創不易,轉載請聯絡Build哥!

image

如果您想與Build哥的關係更近一步,還可以關注“JavaBuild888”,在這裡除了看到《Java成長計劃》系列博文,還有提升工作效率的小筆記、讀書心得、大廠面經、人生感悟等等,歡迎您的加入!

image

相關文章