線程生命周期
摘要:
當線程被創建並啟動以后,它既不是一啟動就進入了執行狀態,也不是一直處於執行狀態。在線程的生命周期中,它要經過新建(New)、就緒(Runnable)、運行(Running)、阻塞(Blocked)和死亡(Dead)5種狀態。尤其是當線程啟動以后,它不可能一直"霸占"着CPU獨自運行,所以CPU需要在多條線程之間切換,於是線程狀態也會多次在運行、阻塞之間切換
1. 新建狀態,當程序使用new關鍵字創建了一個線程之后,該線程就處於新建狀態,此時僅由JVM為其分配內存,並初始化其成員變量的值
2. 就緒狀態,當線程對象調用了start()方法之后,該線程處於就緒狀態。Java虛擬機會為其創建方法調用棧和程序計數器,等待調度運行
3. 運行狀態,如果處於就緒狀態的線程獲得了CPU,開始執行run()方法的線程執行體,則該線程處於運行狀態
4. 阻塞狀態,當處於運行狀態的線程失去所占用資源之后,便進入阻塞狀態
5. 在線程的生命周期當中,線程的各種狀態的轉換過程
一、新建和就緒狀態
當程序使用new關鍵字創建了一個線程之后,該線程就處於新建狀態,此時它和其他的Java對象一樣,僅僅由Java虛擬機為其分配內存,並初始化其成員變量的值。此時的線程對象沒有表現出任何線程的動態特征,程序也不會執行線程的線程執行體。
當線程對象調用了start()方法之后,該線程處於就緒狀態。Java虛擬機會為其創建方法調用棧和程序計數器,處於這個狀態中的線程並沒有開始運行,只是表示該線程可以運行了。至於該線程何時開始運行,取決於JVM里線程調度器的調度。
注意:啟動線程使用start()方法,而不是run()方法。永遠不要調用線程對象的run()方法。調用start0方法來啟動線程,系統會把該run()方法當成線程執行體來處理;但如果直按調用線程對象的run()方法,則run()方法立即就會被執行,而且在run()方法返回之前其他線程無法並發執行。也就是說,系統把線程對象當成一個普通對象,而run()方法也是一個普通方法,而不是線程執行體。需要指出的是,調用了線程的run()方法之后,該線程已經不再處於新建狀態,不要再次調用線程對象的start()方法。只能對處於新建狀態的線程調用start()方法,否則將引發IllegaIThreadStateExccption異常。
調用線程對象的start()方法之后,該線程立即進入就緒狀態——就緒狀態相當於"等待執行",但該線程並未真正進入運行狀態。如果希望調用子線程的start()方法后子線程立即開始執行,程序可以使用Thread.sleep(1) 來讓當前運行的線程(主線程)睡眠1毫秒,1毫秒就夠了,因為在這1毫秒內CPU不會空閑,它會去執行另一個處於就緒狀態的線程,這樣就可以讓子線程立即開始執行。
二、運行和阻塞狀態
2.1 線程調度
如果處於就緒狀態的線程獲得了CPU,開始執行run()方法的線程執行體,則該線程處於運行狀態,如果計算機只有一個CPU。那么在任何時刻只有一個線程處於運行狀態,當然在一個多處理器的機器上,將會有多個線程並行執行;當線程數大於處理器數時,依然會存在多個線程在同一個CPU上輪換的現象。
當一個線程開始運行后,它不可能一直處於運行狀態(除非它的線程執行體足夠短,瞬間就執行結束了)。線程在運行過程中需要被中斷,目的是使其他線程獲得執行的機會,線程調度的細節取決於底層平台所采用的策略。對於采用搶占式策略的系統而言,系統會給每個可執行的線程一個小時間段來處理任務;當該時間段用完后,系統就會剝奪該線程所占用的資源,讓其他線程獲得執行的機會。在選擇下一個線程時,系統會考慮線程的優先級。
所有現代的桌面和服務器操作系統都采用搶占式調度策略,但一些小型設備如手機則可能采用協作式調度策略,在這樣的系統中,只有當一個線程調用了它的sleep()或yield()方法后才會放棄所占用的資源——也就是必須由該線程主動放棄所占用的資源。
2.2 線程阻塞
當發生如下情況時,線程將會進入阻塞狀態
① 線程調用sleep()方法主動放棄所占用的處理器資源
② 線程調用了一個阻塞式IO方法,在該方法返回之前,該線程被阻塞
③ 線程試圖獲得一個同步監視器,但該同步監視器正被其他線程所持有。關於同步監視器的知識、后面將存更深入的介紹
④ 線程在等待某個通知(notify)
⑤ 程序調用了線程的suspend()方法將該線程掛起。但這個方法容易導致死鎖,所以應該盡量避免使用該方法
當前正在執行的線程被阻塞之后,其他線程就可以獲得執行的機會。被阻塞的線程會在合適的時候重新進入就緒狀態,注意是就緒狀態而不是運行狀態。也就是說,被阻塞線程的阻塞解除后,必須重新等待線程調度器再次調度它。
2.3 解除阻塞
針對上面幾種情況,當發生如下特定的情況時可以解除上面的阻塞,讓該線程重新進入就緒狀態:
① 調用sleep()方法的線程經過了指定時間。
② 線程調用的阻塞式IO方法已經返回。
③ 線程成功地獲得了試圖取得的同步監視器。
④ 線程正在等待某個通知時,其他線程發出了個通知。
⑤ 處於掛起狀態的線程被調甩了resdme()恢復方法。
圖 2.1 線程狀態轉換圖
從圖2.1中可以看出,線程從阻塞狀態只能進入就緒狀態,無法直接進入運行狀態。而就緒和運行狀態之間的轉換通常不受程序控制,而是由系統線程調度所決定。當處於就緒狀態的線程獲得處理器資源時,該線程進入運行狀態;當處於運行狀態的線程失去處理器資源時,該線程進入就緒狀態。但有一個方法例外,調用yield()方法可以讓運行狀態的線程轉入就緒狀態。關於yield()方法后面有更詳細的介紐。
三、線程死亡
3.1 死亡狀態
線程會以如下3種方式結束,結束后就處於死亡狀態:
① run()或call()方法執行完成,線程正常結束。
② 線程拋出一個未捕獲的Exception或Error。
③ 直接調用該線程stop()方法來結束該線程——該方法容易導致死鎖,通常不推薦使用。
3.2 程序設計
當主線程結束時,其他線程不受任何影響,並不會隨之結束。一旦子線程啟動起來后,它就擁有和主線程相同的地位,它不會受主線程的影響。為了測試某個線程是否已經死亡,可以調用線程對象的isAlivc()方法,當線程處於就緒、運行、阻塞了種狀態時,該方法將返回true;當線程處於新建、死亡狀態時,該方法將返回false。
不要試圖對一個已經死亡的線程調用start()方法使它重新啟動,死亡就是死亡,該線程將不可再次作為線程執行。
下面程序嘗試對處於死亡狀態的線程再次調用start()。
-
package test;
-
public class StartDead extends Thread {
-
private int i;
-
-
// 重寫 run方法,run方法的方法體就是線程執行體
-
public void run() {
-
for (; i < 100; i++) {
-
System.out.println(getName() + "" + i);
-
}
-
}
-
-
public static void main(String[] args) {
-
// 創建線程對象
-
StartDead sd = new StartDead();
-
for (int i = 0; i < 300; i++) {
-
// 調用 Thread的currentThread方法獲取當前線程
-
System.out.println(Thread.currentThread().getName() + "" + i);
-
if (i == 20) {
-
// 啟動線程
-
sd.start();
-
// 判斷啟動后線程的 isAlive()值,輸出true
-
System.out.println(sd.isAlive());
-
}
-
// 只有當線程處於新建、死亡兩種狀態時 isAlive()方法返回false。
-
// 當 i > 20,則該線程肯定已經啟動過了,如果sd.isAlive()為假時,
-
// 那只能是死亡狀態了。
-
if (i > 20 && !sd.isAlive())
-
-
{
-
// 試圖再次啟動該線程
-
sd.start();
-
}
-
}
-
}
-
}
3.3 運行結果
main 0 main 1 main 2 main 3 main 4 main 5 main 6 main 7 main 8 main 9 main 10 |
main 11 main 12 main 13 main 14 main 15 main 16 main 17 main 18 main 19 main 20 true |
main 21 ………… Thread-0 0 Thread-0 1 Thread-0 2 Thread-0 3 Thread-0 4 Thread-0 5 Thread-0 6 Thread-0 7 Thread-0 8 |
……………… Thread-0 92 Thread-0 93 Thread-0 94 Thread-0 95 Thread-0 96 Thread-0 97 Thread-0 98 Thread-0 99 main 25 Exception |
main→主線程 Thread-0→線程1 Exception→異常 |
上面程序中的粗體字代碼試圖在線程已死亡的情況下再次調用start()方法來啟動該線程。運行上面程序,將引發IllegaIThreadStateException異常,這表明處於死亡狀態的線程無法再次運行了。
如果,您認為閱讀這篇博客讓您有些收獲,不妨點擊一下右下角的【推薦】。
如果,您希望更容易地發現我的新博客,不妨點擊一下左下角的【關注我】。
如果,您對我的博客所講述的內容有興趣,請繼續關注我的后續博客,我是【Sunddenly】。本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利