1. 程式人生 > >Java多執行緒(二)、執行緒的生命週期和狀態控制

Java多執行緒(二)、執行緒的生命週期和狀態控制

、執行緒的生命週期

執行緒狀態轉換圖:


1、新建狀態

用new關鍵字和Thread類或其子類建立一個執行緒物件後,該執行緒物件就處於新生狀態。處於新生狀態的執行緒有自己的記憶體空間,通過呼叫start方法進入就緒狀態(runnable)。

注意:不能對已經啟動的執行緒再次呼叫start()方法,否則會出現Java.lang.IllegalThreadStateException異常。

2、就緒狀態

處於就緒狀態的執行緒已經具備了執行條件,但還沒有分配到CPU,處於執行緒就緒佇列(儘管是採用佇列形式,事實上,把它稱為可執行池而不是可執行佇列。因為cpu的排程不一定是按照先進先出的順序來排程的),等待系統為其分配CPU。等待狀態並不是執行狀態,當系統選定一個等待執行的Thread物件後,它就會從等待執行狀態進入執行狀態,系統挑選的動作稱之為“cpu排程”。一旦獲得CPU,執行緒就進入執行狀態並自動呼叫自己的run方法。

提示:如果希望子執行緒呼叫start()方法後立即執行,可以使用Thread.sleep()方式使主執行緒睡眠一夥兒,轉去執行子執行緒。

3、執行狀態

處於執行狀態的執行緒最為複雜,它可以變為阻塞狀態、就緒狀態和死亡狀態。

處於就緒狀態的執行緒,如果獲得了cpu的排程,就會從就緒狀態變為執行狀態,執行run()方法中的任務。如果該執行緒失去了cpu資源,就會又從執行狀態變為就緒狀態。重新等待系統分配資源。也可以對在執行狀態的執行緒呼叫yield()方法,它就會讓出cpu資源,再次變為就緒狀態。


當發生如下情況是,執行緒會從執行狀態變為阻塞狀態:

①、執行緒呼叫sleep方法主動放棄所佔用的系統資源

②、執行緒呼叫一個阻塞式IO方法,在該方法返回之前,該執行緒被阻塞

③、執行緒試圖獲得一個同步監視器,但更改同步監視器正被其他執行緒所持有

④、執行緒在等待某個通知(notify)

⑤、程式呼叫了執行緒的suspend方法將執行緒掛起。不過該方法容易導致死鎖,所以程式應該儘量避免使用該方法。


當執行緒的run()方法執行完,或者被強制性地終止,例如出現異常,或者呼叫了stop()、desyory()方法等等,就會從執行狀態轉變為死亡狀態。

4、阻塞狀態

處於執行狀態的執行緒在某些情況下,如執行了sleep(睡眠)方法,或等待I/O裝置等資源,將讓出

CPU並暫時停止自己的執行,進入阻塞狀態。 

在阻塞狀態的執行緒不能進入就緒佇列。只有當引起阻塞的原因消除時,如睡眠時間已到,或等待的I/O裝置空閒下來,執行緒便轉入就緒狀態,重新到就緒佇列中排隊等待,被系統選中後從原來停止的位置開始繼續執行。有三種方法可以暫停Threads執行:

5、死亡狀態

當執行緒的run()方法執行完,或者被強制性地終止,就認為它死去。這個執行緒物件也許是活的,但是,它已經不是一個單獨執行的執行緒。執行緒一旦死亡,就不能復生。 如果在一個死去的執行緒上呼叫start()方法,會丟擲java.lang.IllegalThreadStateException異常。


二、執行緒狀態的控制

Java提供了一些便捷的方法用於會執行緒狀態的控制。 .

 void destroy()
          已過時。 該方法最初用於破壞該執行緒,但不作任何清除。它所保持的任何監視器都會保持鎖定狀態。不過,該方法決不會被實現。即使要實現,它也極有可能以suspend() 方式被死鎖。如果目標執行緒被破壞時保持一個保護關鍵系統資源的鎖,則任何執行緒在任何時候都無法再次訪問該資源。如果另一個執行緒曾試圖鎖定該資源,則會出現死鎖。這類死鎖通常會證明它們自己是“凍結”的程序。有關更多資訊,請參閱為何不贊成使用 Thread.stop、Thread.suspend 和 Thread.resume?
 void interrupt()
          中斷執行緒。
 void join()
          等待該執行緒終止。
 void join(long millis)
          等待該執行緒終止的時間最長為 millis 毫秒。
 void join(long millis, int nanos)
          等待該執行緒終止的時間最長為 millis 毫秒 + nanos 納秒。
 void resume()
          已過時。 該方法只與 suspend() 一起使用,但 suspend() 已經遭到反對,因為它具有死鎖傾向。有關更多資訊,請參閱為何不贊成使用 Thread.stop、Thread.suspend 和 Thread.resume?
 void setDaemon(boolean on)
          將該執行緒標記為守護執行緒或使用者執行緒。
 void setPriority(int newPriority)
          更改執行緒的優先順序。
static void sleep(long millis)
          在指定的毫秒數內讓當前正在執行的執行緒休眠(暫停執行),此操作受到系統計時器和排程程式精度和準確性的影響。
static void sleep(long millis, int nanos)
          在指定的毫秒數加指定的納秒數內讓當前正在執行的執行緒休眠(暫停執行),此操作受到系統計時器和排程程式精度和準確性的影響。
 void start()
          使該執行緒開始執行;Java 虛擬機器呼叫該執行緒的 run 方法。
 void stop()
          已過時。 該方法具有固有的不安全性。用 Thread.stop 來終止執行緒將釋放它已經鎖定的所有監視器(作為沿堆疊向上傳播的未檢查ThreadDeath 異常的一個自然後果)。如果以前受這些監視器保護的任何物件都處於一種不一致的狀態,則損壞的物件將對其他執行緒可見,這有可能導致任意的行為。stop 的許多使用都應由只修改某些變數以指示目標執行緒應該停止執行的程式碼來取代。目標執行緒應定期檢查該變數,並且如果該變數指示它要停止執行,則從其執行方法依次返回。如果目標執行緒等待很長時間(例如基於一個條件變數),則應使用interrupt 方法來中斷該等待。有關更多資訊,請參閱為何不贊成使用 Thread.stop、Thread.suspend 和 Thread.resume?
 void stop(Throwable obj)
          已過時。 該方法具有固有的不安全性。有關詳細資訊,請參閱 stop()。該方法的附加危險是它可用於生成目標執行緒未準備處理的異常(包括若沒有該方法該執行緒不太可能丟擲的已檢查的異常)。有關更多資訊,請參閱為何不贊成使用 Thread.stop、Thread.suspend 和 Thread.resume?
 void suspend()
          已過時。 該方法已經遭到反對,因為它具有固有的死鎖傾向。如果目標執行緒掛起時在保護關鍵系統資源的監視器上保持有鎖,則在目標執行緒重新開始以前任何執行緒都不能訪問該資源。如果重新開始目標執行緒的執行緒想在呼叫resume 之前鎖定該監視器,則會發生死鎖。這類死鎖通常會證明自己是“凍結”的程序。有關更多資訊,請參閱為何不贊成使用 Thread.stop、Thread.suspend 和 Thread.resume?
static void yield()
          暫停當前正在執行的執行緒物件,並執行其他執行緒。
 

可以看到很多方法,已經標註為過時的,我們應該儘可能的避免使用它們,而應該重點關注start()、interrupt()、join()、sleep()、yield()等直接控制方法,和setDaemon()、setPriority()等間接控制方法。


1、執行緒睡眠——sleep

如果我們需要讓當前正在執行的執行緒暫停一段時間,並進入阻塞狀態,則可以通過呼叫Thread的sleep方法,從上面可以看到sleep方法有兩種過載的形式,但是使用方法一樣。

比如,我們想要使主執行緒每休眠100毫秒,然後再打印出數字:

[cpp]  view plain  copy
  1. public class Test1 {  
  2.     public static void main(String[] args) throws InterruptedException {  
  3.         for(int i=0;i<100;i++){  
  4.             System.out.println("main"+i);  
  5.             Thread.sleep(100);  
  6.         }  
  7.     }  
  8. }  
可以明顯看到列印的數字在時間上有些許的間隔。

注意如下幾點問題

①、sleep是靜態方法,最好不要用Thread的例項物件呼叫它,因為它睡眠的始終是當前正在執行的執行緒,而不是呼叫它的執行緒物件,它只對正在執行狀態的執行緒物件有效。看下面的例子:

[cpp]  view plain  copy
  1. public class Test1 {  
  2.     public static void main(String[] args) throws InterruptedException {  
  3.         System.out.println(Thread.currentThread().getName());  
  4.         MyThread myThread=new MyThread();  
  5.         myThread.start();  
  6.         myThread.sleep(1000);//這裡sleep的就是main執行緒,而非myThread執行緒  
  7.         Thread.sleep(10);  
  8.         for(int i=0;i<100;i++){  
  9.             System.out.println("main"+i);  
  10.         }  
  11.     }  
  12. }  

②、Java執行緒排程是Java多執行緒的核心,只有良好的排程,才能充分發揮系統的效能,提高程式的執行效率。但是不管程式設計師怎麼編寫排程,只能最大限度的影響執行緒執行的次序,而不能做到精準控制。因為使用sleep方法之後,執行緒是進入阻塞狀態的,只有當睡眠的時間結束,才會重新進入到就緒狀態,而就緒狀態進入到執行狀態,是由系統控制的,我們不可能精準的去幹涉它,所以如果呼叫Thread.sleep(1000)使得執行緒睡眠1秒,可能結果會大於1秒。
[java]  view plain  copy
  1. public class Test1 {  
  2.     public static void main(String[] args) throws InterruptedException {  
  3.         new MyThread().start();  
  4.         new MyThread().start();  
  5.     }  
  6. }  
  7.   
  8. class MyThread extends Thread {  
  9.     @Override  
  10.     public void run() {  
  11.         for (int i = 0; i < 3; i++) {  
  12.             System.out.println(this.getName()+"執行緒" + i + "次執行!");  
  13.             try {  
  14.                 Thread.sleep(50);  
  15.             } catch (InterruptedException e) {  
  16.                 e.printStackTrace();  
  17.             }  
  18.         }  
  19.     }  
  20. }  
看某一次的執行結果: [java]  view plain  copy
  1. Thread-0執行緒0次執行!  
  2. Thread-1執行緒0次執行!  
  3. Thread-1執行緒1次執行!  
  4. Thread-0執行緒1次執行!  
  5. Thread-0執行緒2次執行!  
  6. Thread-1執行緒2次執行!  
可以看到,執行緒0首先執行,然後執行緒1執行一次,又了執行一次。可以看到它並不是按照sleep的順序執行的。


2、執行緒讓步——yield

yield()方法和sleep()方法有點相似,它也是Thread類提供的一個靜態的方法,它也可以讓當前正在執行的執行緒暫停,讓出cpu資源給其他的執行緒。但是和sleep()方法不同的是,它不會進入到阻塞狀態,而是進入到就緒狀態。yield()方法只是讓當前執行緒暫停一下,重新進入就緒的執行緒池中,讓系統的執行緒排程器重新排程器重新排程一次,完全可能出現這樣的情況:當某個執行緒呼叫yield()方法之後,執行緒排程器又將其排程出來重新進入到執行狀態執行。

實際上,當某個執行緒呼叫了yield()方法暫停之後,優先順序與當前執行緒相同,或者優先順序比當前執行緒更高的就緒狀態的執行緒更有可能獲得執行的機會,當然,只是有可能,因為我們不可能精確的干涉cpu排程執行緒。

yield的用法:

[java]  view plain  copy
  1. public class Test1 {  
  2.     public static void main(String[] args) throws InterruptedException {  
  3.         new MyThread("低階"1).start();  
  4.         new MyThread("中級"5).start();  
  5.         new MyThread("高階"10).start();  
  6.     }  
  7. }  
  8.   
  9. class MyThread extends Thread {  
  10.     public MyThread(String name, int pro) {  
  11.         super(name);// 設定執行緒的名稱  
  12.         this.setPriority(pro);// 設定優先順