【多線程學習筆記3】線程的生命週期與控制

1 線程的生命週期

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

1.1 新建和就緒狀態

新建狀態:當程序使用new關鍵字創建了一個線程之後,該線程就處於新建狀態,此時它和其他的Java對象一樣,僅僅由JVM爲其分配內存,並初始化其成員變量的值。

就緒狀態:當線程對象調用了start()方法之後,該線程處於就緒狀態,JVM會爲其創建方法調用棧和程序計數器,處於這個狀態中的線程並沒有開始運行,只是表示該線程可以運行了。至於何時運行,取決於JVM裏線程調度器的調度。

注意:
(1)啓動線程使用start()方法,而不是run()方法!永遠不要調用線程對象的run()方法!調用start()方法來啓動線程,系統會把對應的run()方法當做線程執行體來處理,但如果直接調用線程對象的run()方法,則run()方法立即就會執行,而且在run()方法返回之前其他線程無法併發執行。簡而言之,就是啓動線程的正確方法是調用Thread對象的start()方法,而不是直接調用run()方法,否則就不是新開線程了,而是在同步單線執行了。
(2)只能對處於新建狀態的線程調用start()方法,否則將引發IllegalThreadStateException異常。

1.2 運行和阻塞狀態

運行狀態:如果處於就緒狀態的線程獲得了CPU,開始執行run()方法的線程執行體,則該線程處於運行狀態,如果計算機只有一個CPU,那麼在任何時刻只有一個線程處於運行狀態。

當一個線程開始運行後,它很難一直處於運行狀態,線程在運行過程中需要被中斷,目的是使其他線程獲得執行的機會,線程調度的細節取決於底層平臺所採用的策略。說明對於搶佔式策略,在選擇下一個線程時,系統會考慮線程的優先級。

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

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

(1)線程調用sleep()方法主動放棄所佔用的處理器資源。
(2)線程調用了一個阻塞式IO方法,在該方法返回之前,該線程被阻塞。
(3)線程試圖獲得一個同步鎖,但該同步鎖被其他線程所持有。
(4)線程在等待某個通知(notify)。
(5)程序調用了線程的suspend()方法將線程掛起。(容易引起死鎖,應儘量避免使用!)

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

(1)調用sleep()方法的線程經過了指定的時間。
(2)線程調用的阻塞式IO方法已經返回。
(3)線程成功獲得了同步鎖。
(4)線程正在等待某個通知時,其他線程發出了一個通知。
(5)處於掛起狀態的線程被調用了resume()恢復方法。

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

1.3 線程死亡

以下三種方式結束,線程即進入死亡狀態:
(1)run()或call()方法執行完成,線程正常結束。
(2)線程拋出一個未捕獲的Exception或Error。
(3)直接調用該線程的stop()方法來結束該線程,該方法容易導致死鎖,不推薦使用。

注意:不要試圖對一個已經死亡的線程調用start()方法使它重新啓動,死亡就是死亡,該線程將不可再次作爲線程執行,否則將引發IllegalThreadStateException異常。

2 控制線程

2.1 join線程

join()方法:讓一個線程等待另一個線程完成。當在某個程序執行流中調用其他線程的join()方法時,調用線程將被阻塞,直到被join()方法加入的join線程執行完爲止。

2.2 後臺線程

後臺線程也叫守護線程,JVM的垃圾回收線程就是典型的後臺線程。後臺線程有個特徵,如果所有的前臺線程都死亡,後臺線程也會自動死亡。通過調用Thread對象的setDaemon(true)方法可以將指定線程設置成後臺線程,同時Thread類還提供了一個isDaemon()方法用於判斷執行的線程是否爲後臺線程。

注意:將一個線程設置爲後臺線程,必須要在該線程啓動之前設置,也就是說,setDaemon(true)方法必須在start()方法之前調用,否則將引發IllegalThreadStateException異常。

2.3 線程睡眠:sleep

線程調用sleep()方法進入阻塞狀態,在其睡眠時間段內,該線程不會獲得執行機會,即便系統中沒有其他可執行的線程,處於sleep()中的線程也不會執行。

2.4 線程讓步:yield

yield()方法可以讓當前正在執行的線程暫停,但它不會阻塞該線程,它只是將該線程轉入就緒狀態。yield()只是讓當前線程暫停一下,讓系統的線程調度器重新調度一次,完全可能的情況是,當某個線程調用了yield()方法暫停之後,線程調度器又將其調度出來重新執行。

實際上,當某個線程調用yield()方法之後,只有優先級與當前線程相同或者優先級比當前線程更高的處於就緒狀態的線程纔會獲得執行的機會。在多CPU並行的環境下,yield()方法的功能很多時候並不明顯。

2.5 改變線程優先級

每個線程執行時都具有一定的優先級,優先級高的線程獲得較多的執行機會,而優先級低的線程則獲得較少的執行機會。每個線程默認的優先級都與創建它的父線程的優先級相同。

注意:由於不同操作系統的支持不同,應該儘量避免直接爲線程指定優先級,而應該使用靜態常量來設置優先級,這樣可以保證程序具有最好的可移植性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章