JAVA多線程之線程的生命周期

來源: https://www.cnblogs.com/albertrui/p/8383799.html

一、前言

  當線程被創建并啟動以后,它既不是一啟動就進入了執行狀態,也不是一直處于執行狀態。在線程的生命周期中,它要經過新建(New)、就緒(Runnable)、運行(Running)、阻塞(Blocked)和死亡(Dead)5種狀態。尤其是當線程啟動以后,它不可能一直"霸占"著CPU獨自運行,所以CPU需要在多條線程之間切換,于是線程狀態也會多次在運行、阻塞之間切換

新建狀態,當程序使用new關鍵字創建了一個線程之后,該線程就處于新建狀態,此時僅由JVM為其分配內存,并初始化其成員變量的值

就緒狀態,當線程對象調用了start()方法之后,該線程處于就緒狀態。Java虛擬機會為其創建方法調用棧和程序計數器,等待調度運行

運行狀態,如果處于就緒狀態的線程獲得了CPU,開始執行run()方法的線程執行體,則該線程處于運行狀態

阻塞狀態,當處于運行狀態的線程失去所占用資源之后,便進入阻塞狀態

死亡狀態,線程在run()方法執行結束后進入死亡狀態。此外,如果線程執行了interrupt()或stop()方法,那么它也會以異常退出的方式進入死亡狀態。

二、新建和就緒狀態

  當程序使用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不會空閑,它會去執行另一個處于就緒狀態的線程,這樣就可以讓子線程立即開始執行。

三、運行和阻塞狀態

3.1 線程調度

  如果處于就緒狀態的線程獲得了CPU,開始執行run()方法的線程執行體,則該線程處于運行狀態,如果計算機只有一個CPU。那么在任何時刻只有一個線程處于運行狀態,當然在一個多處理器的機器上,將會有多個線程并行執行;當線程數大于處理器數時,依然會存在多個線程在同一個CPU上輪換的現象。

  當一個線程開始運行后,它不可能一直處于運行狀態(除非它的線程執行體足夠短,瞬間就執行結束了)。線程在運行過程中需要被中斷,目的是使其他線程獲得執行的機會,線程調度的細節取決于底層平臺所采用的策略。對于采用搶占式策略的系統而言,系統會給每個可執行的線程一個小時間段來處理任務;當該時間段用完后,系統就會剝奪該線程所占用的資源,讓其他線程獲得執行的機會。在選擇下一個線程時,系統會考慮線程的優先級。

  所有現代的桌面和服務器操作系統都采用搶占式調度策略,但一些小型設備如手機則可能采用協作式調度策略,在這樣的系統中,只有當一個線程調用了它的sleep()或yield()方法后才會放棄所占用的資源——也就是必須由該線程主動放棄所占用的資源。

3.2 線程阻塞

  當發生如下情況時,線程將會進入阻塞狀態

  ① 線程調用sleep()方法主動放棄所占用的處理器資源

  ② 線程調用了一個阻塞式IO方法,在該方法返回之前,該線程被阻塞

  ③ 線程試圖獲得一個同步監視器,但該同步監視器正被其他線程所持有。關于同步監視器的知識、后面將有深入的介紹

  ④ 線程在等待某個通知(notify)

  ⑤ 程序調用了線程的suspend()方法將該線程掛起。但這個方法容易導致死鎖,所以應該盡量避免使用該方法

  當前正在執行的線程被阻塞之后,其他線程就可以獲得執行的機會。被阻塞的線程會在合適的時候重新進入就緒狀態,注意是就緒狀態而不是運行狀態。也就是說,被阻塞線程的阻塞解除后,必須重新等待線程調度器再次調度它。

2.3 解除阻塞

  針對上面幾種情況,當發生如下特定的情況時可以解除上面的阻塞,讓該線程重新進入就緒狀態:

  ① 調用sleep()方法的線程經過了指定時間。

  ② 線程調用的阻塞式IO方法已經返回。

  ③ 線程成功地獲得了試圖取得的同步監視器。

  ④ 線程正在等待某個通知時,其他線程發出了個通知。

  ⑤ 處于掛起狀態的線程被調甩了resdme()恢復方法。

  圖 3.1 線程狀態轉換圖

  從圖3.1中可以看出,線程從阻塞狀態只能進入就緒狀態,無法直接進入運行狀態。而就緒和運行狀態之間的轉換通常不受程序控制,而是由系統線程調度所決定。當處于就緒狀態的線程獲得處理器資源時,該線程進入運行狀態;當處于運行狀態的線程失去處理器資源時,該線程進入就緒狀態。但有一個方法例外,調用yield()方法可以讓運行狀態的線程轉入就緒狀態。關于yield()方法后面有更詳細的介紐。

四、線程死亡

4.1 死亡狀態

  線程會以如下3種方式結束,結束后就處于死亡狀態:

  ① run()或call()方法執行完成,線程正常結束。

  ② 線程拋出一個未捕獲的Exception或Error。

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

4.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();

?????????}

??????}

???}

}

4.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異常,這表明處于死亡狀態的線程無法再次運行了。一、前言

  當線程被創建并啟動以后,它既不是一啟動就進入了執行狀態,也不是一直處于執行狀態。在線程的生命周期中,它要經過新建(New)、就緒(Runnable)、運行(Running)、阻塞(Blocked)和死亡(Dead)5種狀態。尤其是當線程啟動以后,它不可能一直"霸占"著CPU獨自運行,所以CPU需要在多條線程之間切換,于是線程狀態也會多次在運行、阻塞之間切換

新建狀態,當程序使用new關鍵字創建了一個線程之后,該線程就處于新建狀態,此時僅由JVM為其分配內存,并初始化其成員變量的值

就緒狀態,當線程對象調用了start()方法之后,該線程處于就緒狀態。Java虛擬機會為其創建方法調用棧和程序計數器,等待調度運行

運行狀態,如果處于就緒狀態的線程獲得了CPU,開始執行run()方法的線程執行體,則該線程處于運行狀態

阻塞狀態,當處于運行狀態的線程失去所占用資源之后,便進入阻塞狀態

死亡狀態,線程在run()方法執行結束后進入死亡狀態。此外,如果線程執行了interrupt()或stop()方法,那么它也會以異常退出的方式進入死亡狀態。

二、新建和就緒狀態

  當程序使用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不會空閑,它會去執行另一個處于就緒狀態的線程,這樣就可以讓子線程立即開始執行。

三、運行和阻塞狀態

3.1 線程調度

  如果處于就緒狀態的線程獲得了CPU,開始執行run()方法的線程執行體,則該線程處于運行狀態,如果計算機只有一個CPU。那么在任何時刻只有一個線程處于運行狀態,當然在一個多處理器的機器上,將會有多個線程并行執行;當線程數大于處理器數時,依然會存在多個線程在同一個CPU上輪換的現象。

  當一個線程開始運行后,它不可能一直處于運行狀態(除非它的線程執行體足夠短,瞬間就執行結束了)。線程在運行過程中需要被中斷,目的是使其他線程獲得執行的機會,線程調度的細節取決于底層平臺所采用的策略。對于采用搶占式策略的系統而言,系統會給每個可執行的線程一個小時間段來處理任務;當該時間段用完后,系統就會剝奪該線程所占用的資源,讓其他線程獲得執行的機會。在選擇下一個線程時,系統會考慮線程的優先級。

  所有現代的桌面和服務器操作系統都采用搶占式調度策略,但一些小型設備如手機則可能采用協作式調度策略,在這樣的系統中,只有當一個線程調用了它的sleep()或yield()方法后才會放棄所占用的資源——也就是必須由該線程主動放棄所占用的資源。

3.2 線程阻塞

  當發生如下情況時,線程將會進入阻塞狀態

  ① 線程調用sleep()方法主動放棄所占用的處理器資源

  ② 線程調用了一個阻塞式IO方法,在該方法返回之前,該線程被阻塞

  ③ 線程試圖獲得一個同步監視器,但該同步監視器正被其他線程所持有。關于同步監視器的知識、后面將有深入的介紹

  ④ 線程在等待某個通知(notify)

  ⑤ 程序調用了線程的suspend()方法將該線程掛起。但這個方法容易導致死鎖,所以應該盡量避免使用該方法

  當前正在執行的線程被阻塞之后,其他線程就可以獲得執行的機會。被阻塞的線程會在合適的時候重新進入就緒狀態,注意是就緒狀態而不是運行狀態。也就是說,被阻塞線程的阻塞解除后,必須重新等待線程調度器再次調度它。

2.3 解除阻塞

  針對上面幾種情況,當發生如下特定的情況時可以解除上面的阻塞,讓該線程重新進入就緒狀態:

  ① 調用sleep()方法的線程經過了指定時間。

  ② 線程調用的阻塞式IO方法已經返回。

  ③ 線程成功地獲得了試圖取得的同步監視器。

  ④ 線程正在等待某個通知時,其他線程發出了個通知。

  ⑤ 處于掛起狀態的線程被調甩了resdme()恢復方法。

  圖 3.1 線程狀態轉換圖


  從圖3.1中可以看出,線程從阻塞狀態只能進入就緒狀態,無法直接進入運行狀態。而就緒和運行狀態之間的轉換通常不受程序控制,而是由系統線程調度所決定。當處于就緒狀態的線程獲得處理器資源時,該線程進入運行狀態;當處于運行狀態的線程失去處理器資源時,該線程進入就緒狀態。但有一個方法例外,調用yield()方法可以讓運行狀態的線程轉入就緒狀態。關于yield()方法后面有更詳細的介紐。

四、線程死亡

4.1 死亡狀態

  線程會以如下3種方式結束,結束后就處于死亡狀態:

  ① run()或call()方法執行完成,線程正常結束。

  ② 線程拋出一個未捕獲的Exception或Error。

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

4.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();

?????????}

??????}

???}

}

4.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異常,這表明處于死亡狀態的線程無法再次運行了。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Java多線程學習 [-] 一擴展javalangThread類 二實現javalangRunnable接口 三T...
    影馳閱讀 2,987評論 1 18
  • 本文主要講了java中多線程的使用方法、線程同步、線程數據傳遞、線程狀態及相應的一些線程函數用法、概述等。 首先講...
    李欣陽閱讀 2,493評論 1 15
  • 林炳文Evankaka原創作品。轉載自http://blog.csdn.net/evankaka 本文主要講了ja...
    ccq_inori閱讀 670評論 0 4
  • ??一個任務通常就是一個程序,每個運行中的程序就是一個進程。當一個程序運行時,內部可能包含了多個順序執行流,每個順...
    OmaiMoon閱讀 1,706評論 0 12
  • 一、認識多任務、多進程、單線程、多線程 要認識多線程就要從操作系統的原理說起。 以前古老的DOS操作系統(V 6....
    GT921閱讀 1,025評論 0 3