寫在開頭
在線程的生命周期中,不同狀態之間切換時,可以通過調用sleep()、wait()、join()、yield()等方法進行線程狀態控制,針對這一部分知識點,面試官們也會做做文章,比如問你這些方法的作用以及之間的區別。
那么今天我們就一起來總結一下這幾個方法的作用及區別,先畫一個思維導圖梳理一下,便于理解與記憶,爭取在被問到這個點時徹底征服面試官?。?code>圖片可保存??垂?/code>)
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成長計劃》系列博文,還有提升工作效率的小筆記、讀書心得、大廠面經、人生感悟等等,歡迎您的加入!