Java多線程學習(三)---線程的生命周期


線程生命周期

摘要

當線程被創建並啟動以后,它既不是一啟動就進入了執行狀態,也不是一直處於執行狀態。在線程的生命周期中,它要經過新建(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()方法執行完成,線程正常結束。

線程拋出一個未捕獲的ExceptionError

直接調用該線程stop()方法來結束該線程——該方法容易導致死鎖,通常不推薦使用。

3.2 程序設計

主線程結束時,其他線程不受任何影響,並不會隨之結束。一旦子線程啟動起來后,它就擁有和主線程相同的地位,它不會受主線程的影響。為了測試某個線程是否已經死亡,可以調用線程對象的isAlivc()方法,當線程處於就緒運行阻塞了種狀態時,該方法將返回true;當線程處於新建死亡狀態時,該方法將返回false

不要試圖對一個已經死亡的線程調用start()方法使它重新啟動,死亡就是死亡,該線程將不可再次作為線程執行。

下面程序嘗試對處於死亡狀態的線程再次調用start()。

  1. package test;
  2. public class StartDead extends Thread {
  3.    private int i;
  4.  
  5.    // 重寫 run方法,run方法的方法體就是線程執行體
  6.    public void run() {
  7.       for (; i < 100; i++) {
  8.          System.out.println(getName() + "" + i);
  9.       }
  10.    }
  11.  
  12.    public static void main(String[] args) {
  13.       // 創建線程對象
  14.       StartDead sd = new StartDead();
  15.       for (int i = 0; i < 300; i++) {
  16.          // 調用 ThreadcurrentThread方法獲取當前線程
  17.          System.out.println(Thread.currentThread().getName() + "" + i);
  18.          if (i == 20) {
  19.             // 啟動線程
  20.             sd.start();
  21.             // 判斷啟動后線程的 isAlive()值,輸出true
  22.             System.out.println(sd.isAlive());
  23.          }
  24.          // 只有當線程處於新建、死亡兩種狀態時 isAlive()方法返回false
  25.          // i > 20,則該線程肯定已經啟動過了,如果sd.isAlive()為假時,
  26.          // 那只能是死亡狀態了。
  27.          if (i > 20 && !sd.isAlive())
  28.  
  29.          {
  30.             // 試圖再次啟動該線程
  31.             sd.start();
  32.          }
  33.       }
  34.    }
  35. }

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】。

本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM