Java 线程的生命周期
當線程被創建并啟動以后,它既不是一啟動就進入了執行狀態,也不是一直處于執行狀態,在線程的生命周期中,它要經過新建(New)、就緒(Runnable)、運行(Running)、阻塞(Blocked)和死亡(Dead)5種狀態。尤其是當線程啟動以后,它不可能一直“霸占”著CPU 獨自運行,所以 CPU 需要在多條線程之間切換,于是線程狀態也會多次在運行、阻塞之間切換。
新建和就緒狀態
當程序使用 new 關鍵字創建了一個線程之后,該線程就處于新建狀態,此時它和其他的 Java 對象一樣,僅僅由 Java 虛擬機為其分配內存,并初始化其成員變量的值。此時的線程對象沒有表現出任何線程的動態特征,程序也不會執行線程的線程執行體。
當線程對象調用了 start() 方法之后,該線程處于就緒狀態, Java 虛擬機會為其創建方法調用棧和程序計數器,處于這個狀態中的線程并沒有開始運行,只是表示該線程可以運行了。至于該線程何時開始運行,取決于 JVM 里線程調度器的調度。
注意:啟動線程使用 start()方法,而不是 run()方法!永遠不要調用線程對象的 run()方法!調用 start()方法來啟動線程,系統會 把該 run()方法當成線程執行體來處理;但如果直接調用線程對象的 run()方法,則 run()方法立即就會被執行,而且在 run()方法返回之前其他線程無法并發執行——也就是說,如果直接調用線程對象的 run()方法,系統把線程對象當成一個普通對象,而 run()方法也是一個普通方法,而不是線程執行體。
public class InvokeRun extends Thread {private int i = 0;// 重寫run()方法,方法體就是線程執行體public void run() {for (; i < 100; i++) {// 直接調用run()方法時,Thread的this.getName()返回的是該對象的名字// 而不是當前線程的名字// 使用Thread.currentThread().getName()總是獲取當前線程的名字System.out.println(Thread.currentThread().getName() + " " + i);}}public static void main(String[] args) {for (int i = 0; i < 100; i++) {// 調用Thread的currentThread()方法獲取當前線程System.out.println(Thread.currentThread().getName() + " " + i);if (i == 20) {//直接調用線程對象的run()方法 //系統會把線程對象當成普通對象,把run()方法當成普通方法//所以下面兩行代碼并不會啟動兩個線程,而是依次執行兩個run()方法new InvokeRun().run();new InvokeRun().run();}}}}上面程序創建線程對象后直接調用了線程對象的 run() 方法(如粗體字代碼所示),程序運行的結果是整個程序只有一個線程:主線程。還有一點需要指出,如果直接調用線程對象的 run() 方法,則 run() 方法里不能直接通過 getName() 方法來獲得當前執行線程的名字,而是需要使用 Thread.currentThread()?方法先獲得當前線程,再調用線程對象的 getName() 方法來獲得線程的名字。
通過上面程序不難看出,啟動線程的正確方法是調用 Thread 對象的 start()方法,而不是直接調用?run()方法,否則就變成單線程程序了。
需要指出的是,調用了線程的 run()方法之后,該線程已經不再處于新建狀態,不要再次調用線程對象的 start() 方法。
注意:只能對處于新建狀態的線程調用 start() 方法,否則將引發IllegalThreadStateException異常
調用線程對象的 start()?方法之后,該線程立即進入就緒狀態——就緒狀態相當于“等待執行”,但該線程并未真正進入運行狀態
提示:如果希望調用子線程的 start() 方法后子線程立即開始執行,程序可以使用 Thread.sleep(1)?來讓當前運行的線程(主線程)睡眠1毫秒——1毫秒就夠了,因為在這1毫秒內 CPU 不會空閑,它會去執行另一個處于就緒狀態的線程,這樣就可以讓子線程立即開始執行
?
運行和阻塞狀態
如果處于就緒狀態的線程獲得了 CPU ,開始執行 run() 方法的線程執行體,則該線程處于運行狀態,
如果計算機只有一個 CPU ,那么在任何時刻只有一個線程處于運行狀態。當然,在一個多處理器的機器上,將會有多個線程并行(注意是并行: parallel )執行;當線程數大于處理器數時,依然會存在多個線程在同一個 CPU 上輪換的現象。
當一個線程開始運行后,它不可能一直處于運行狀態(除非它的線程執行體足夠短,瞬間就執行結束了),線程在運行過程中需要被中斷,目的是使其他線程獲得執行的機會,線程調度的細節取決于底層平臺所釆用的策略。對于釆用搶占式策略的系統而言,系統會給每個可執行的線程一個小時間段來處理任務;當該時間段用完后,系統就會剝奪該線程所占用的資源,讓其他線程獲得執行的機會。在選擇下一個線程時,系統會考慮線程的優先級。
所有現代的桌面和服務器操作系統都釆用搶占式調度策略,但一些小型設備如手機則可能采用協作式調度策略,在這樣的系統中,只有當一個線程調用了它的 sleep() 或 yield() 方法后才會放棄所占用的資源——也就是必須由該線程主動放棄所占用的資源。
當發生如下情況時,線程將會進入阻塞狀態。
1、線程調用 sleep()方法主動放棄所占用的處理器資源。
2、線程調用了一個阻塞式 IO 方法,在該方法返回之前,該線程被阻塞。
3、線程試圖獲得一個同步監視器,但該同步監視器正被其他線程所持有。
4、線程在等待某個通知(notify)。
5、程序調用了線程的 suspend() 方法將該線程掛起。但這個方法容易導致死鎖,所以應該盡量避免使用該方法。
當前正在執行的線程被阻塞之后,其他線程就可以獲得執行的機會。被阻塞的線程會在合適的時候重新進入就緒狀態,注意是就緒狀態而不是運行狀態。也就是說,被阻塞線程的阻塞解除后,必須重新等待線程調度器再次調度它。
針對上面幾種情況,當發生如下特定的情況時可以解除上面的阻塞,讓該線程重新進入就緒狀態。
1、調用 sleep() 方法的線程經過了指定時間。
2、線程調用的阻塞式IO方法已經返回。
3、線程成功地獲得了試圖取得的同步監視器。
4、線程正在等待某個通知時,其他線程發出了一個通知。
5、處于掛起狀態的線程被調用了 resume() 恢復方法。
從上圖可以看出,線程從阻塞狀態只能進入就緒狀態,無法直接進入運行狀態。而就緒和運行狀態之間的轉換通常不受程序控制,而是由系統線程調度所決定,當處于就緒狀態的線程獲得處理器資源時,該線程進入運行狀態;當處于運行狀態的線程失去處理器資源時,該線程進入就緒狀態。但有一個方法例外,調用 yield ()方法可以讓運行狀態的線程轉入就緒狀態。
?
線程死亡
線程會以如下三種方式結束,結束后就處于死亡狀態。
1、 run() 或 call() 方法執行完成,線程正常結束。
2、線程拋出一個未捕獲的 Exception 或 Error 。
3、直接調用該線程的 stop() 方法來結束該線程——該方法容易導致死鎖,通常不推薦使用。
注意:當主線程結束時,其他線程不受任何影響,并不會隨之結束。一旦子線程啟動起來后,它就擁有和主線程相同的地位,它不會受主線程的影響。
為了測試某個線程是否已經死亡,可以調用線程對象的 isAlive() 方法,當線程處于就緒、運行、阻塞三種狀態時,該方法將返回 true ;當線程處于新建、死亡兩種狀態時,該方法將返回 false 。
注意:不要試圖對一個已經死亡的線程調用 start() 方法使它重新啟動,死亡就是死亡,該線程將不可再次作為線程執行。
public class StartDead extends Thread{private int i = 0;// 重寫run()方法,方法體就是線程執行體public void run() {for (; i < 100; i++) {System.out.println(Thread.currentThread().getName() + " " + i);}}public static void main(String[] args) {StartDead sd = new StartDead();for (int i = 0; i < 300; i++) {System.out.println(Thread.currentThread().getName() + " " + i);if (i == 20) {//啟動線程 sd.run();//判斷啟動后的線程的isAlive()值,輸出 true System.out.println(sd.isAlive());}//當線程處于新建、死亡兩種狀態時,isAlive()方法返回 false//當i>20時,該線程肯定已經啟動過了,如果sd.isAlive()為假時//那就是死亡狀態了if(i>20 && !sd.isAlive()) {//試圖再次啟動該線程 sd.start();}}} }注意:不要對處于死亡狀態的線程調用 start() 方法,程序只能對新建狀態的線程調用 start()方法,對新建狀態的線程兩次調用 start() 方法也是錯誤的。這都會引發 IllegalThreadStateException異常。
?
轉載于:https://www.cnblogs.com/jwen1994/p/10513953.html
總結
以上是生活随笔為你收集整理的Java 线程的生命周期的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 003 Rabbitmq中交换机的类型
- 下一篇: 连号区间数(2013年第四届c/c++