寫在開頭
線上程的生命週期中,不同狀態之間切換時,可以透過呼叫sleep()、wait()、join()、yield()等方法進行執行緒狀態控制,針對這一部分知識點,面試官們也會做做文章,比如問你這些方法的作用以及之間的區別。
那麼今天我們就一起來總結一下這幾個方法的作用及區別,先畫一個思維導圖梳理一下,便於理解與記憶,爭取在被問到這個點時徹底征服面試官!(圖片可儲存常看哈
)
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()的區別?
- sleep() 是 Thread 類的靜態本地方法;wait() 是Object類的成員本地方法;
- JDK1.8 sleep() wait() 均需要捕獲 InterruptedException 異常;
- sleep() 方法可以在任何地方使用;wait() 方法則只能在同步方法或同步程式碼塊中使用;
- sleep() 會休眠當前執行緒指定時間,釋放 CPU 資源,不釋放物件鎖,休眠時間到自動甦醒繼續執行;wait() 方法放棄持有的物件鎖,進入等待佇列,當該物件被呼叫 notify() / notifyAll() 方法後才有機會競爭獲取物件鎖,進入執行狀態。
(2)sleep()與yield()的區別?
- sleep() 方法給其他執行緒執行機會時不考慮執行緒的優先順序;yield() 方法只會給相同優先順序或更高優先順序的執行緒執行的機會;
- sleep() 方法宣告丟擲 InterruptedException;yield() 方法沒有宣告丟擲異常;
- 執行緒執行 sleep() 方法後進入超時等待狀態;執行緒執行 yield() 方法轉入就緒狀態,可能馬上又得得到執行;
- sleep() 方法需要指定時間引數;yield() 方法出讓 CPU 的執行權時間由 JVM 控制。
(3)sleep()與join()的區別?
- JDK1.8 sleep() join() 均需要捕獲 InterruptedException 異常;
- sleep()是Thread的靜態本地方法,join()是Thread的普通方法;
- sleep()不會釋放鎖資源,join()底層是wait方法,會釋放鎖。
結尾彩蛋
如果本篇部落格對您有一定的幫助,大家記得留言+點贊+收藏呀。原創不易,轉載請聯絡Build哥!
如果您想與Build哥的關係更近一步,還可以關注“JavaBuild888”,在這裡除了看到《Java成長計劃》系列博文,還有提升工作效率的小筆記、讀書心得、大廠面經、人生感悟等等,歡迎您的加入!