Java併發編程實踐之線程的基本控制

  線程創建後,可以執行start()方法啓動線程,根據線程任務的特性和線程之間的協調性要求,需要對線程進行控制。對線程的控制通常是通過調用Thread對象的方法實現的,主要有sleep()、suspend()、resume()、join()、interrupt()和stop方法。一般情況下方法的調用會引起線程狀態的轉變。

1、使用Sleep暫停執行

     Thread.sleep()使當前線程的執行暫停一段指定的時間,這可以有效的使應用程序的其他線程或者運行在計算機上的其他進程可以使用處理器時間。該方法不會放棄CPU之外的其他資源。Sleep有兩個重載的版本,一個以毫秒指定睡眠時間,另一個以納秒指定睡眠時間,但並不保證這些睡眠時間的精確性,因爲它們收到系統計時器和調度程序精度和準確性的影響。另外中斷(interrupt)可以終止睡眠時間,在任何情況下,都不能假設調用sleep就會按照知道指定

的時間準確的掛起線程。

Java代碼  收藏代碼
  1. public class TestSleep {  
  2.     public static void main(String[] arg) {  
  3.         String[] args={"one","two","three","four"};  
  4.         long start=System.nanoTime();  
  5.         for(int i=0;i<args.length;i++){  
  6.             System.out.println(args[i]);  
  7.             //休眠主線程  
  8.             try {  
  9.                 Thread.sleep(1000);  
  10.             } catch (InterruptedException e) {  
  11.                 e.printStackTrace();  
  12.             }  
  13.         }  
  14.         long end=System.nanoTime();  
  15.         System.out.println("總的時間:"+(end-start)/1000000);  
  16.     }  
  17. }  

     需要注意的是,sleep()方法聲明可能會拋出InterruptedException異常,當另一個線程中斷了已經啓動sleep的當前線程時機會拋出這個異常。上面的程序只有主線程,不需要考慮這個問題。

2、使用join等待另外一個線程結束

     join方法讓一個線程等待另一個線程的完成,如果t1、t2是兩個Thread對象,在t1中調用t2.join(),會導致t1線程暫停執行,直到t2的線程終止。join的重載版本運行程序員指定等待的時間,當時和sleep一樣,這個時間是不精確地。

Java代碼  收藏代碼
  1. public class TestJoin extends Thread{  
  2.     static int result=0;  
  3.     public TestJoin(String name){  
  4.         super(name);  
  5.     }  
  6.     public static void main(String[] args) {  
  7.         System.out.println("主線程執行");  
  8.         Thread t=new TestJoin("計算線程");  
  9.         t.start();  
  10.         System.out.println("result:"+result);  
  11.         try {  
  12.             long start=System.nanoTime();  
  13.             t.join();  
  14.             long end=System.nanoTime();  
  15.             System.out.println((end-start)/1000000+"毫秒後:"+result);  
  16.         } catch (Exception e) {  
  17.             e.printStackTrace();  
  18.         }  
  19.     }  
  20.     @Override  
  21.     public void run() {  
  22.         System.out.println(this.getName()+"開始計算:...");  
  23.         try {  
  24.             Thread.sleep(4000);  
  25.         } catch (InterruptedException e) {  
  26.             // TODO Auto-generated catch block  
  27.             e.printStackTrace();  
  28.         }  
  29.         result=(int) (Math.random()*10000);  
  30.         System.out.println(this.getName()+"計算結束:...");  
  31.     }  
  32. }  

執行結果如下:

Java代碼  收藏代碼
  1. 主線程執行  
  2. result:0  
  3. 計算線程開始計算:...  
  4. 計算線程計算結束:...  
  5. 3993毫秒後:4462  

上面的程序中,計算線程在計算的時候休眠了4000毫秒,在主線程中調用了t.join()後,主線程等待計算線程執行結束,然後輸出結果。

可以把t.join()修改爲t.join(2000)。

Java代碼  收藏代碼
  1. public class TestJoin extends Thread{  
  2.     static int result=0;  
  3.     public TestJoin(String name){  
  4.         super(name);  
  5.     }  
  6.     public static void main(String[] args) {  
  7.         System.out.println("主線程執行");  
  8.         Thread t=new TestJoin("計算線程");  
  9.         t.start();  
  10.         System.out.println("result:"+result);  
  11.         try {  
  12.             long start=System.nanoTime();  
  13.             t.join(2000);  
  14.             long end=System.nanoTime();  
  15.             System.out.println((end-start)/1000000+"毫秒後:"+result);  
  16.         } catch (Exception e) {  
  17.             e.printStackTrace();  
  18.         }  
  19.     }  
  20.     @Override  
  21.     public void run() {  
  22.         System.out.println(this.getName()+"開始計算:...");  
  23.         try {  
  24.             Thread.sleep(4000);  
  25.         } catch (InterruptedException e) {  
  26.             // TODO Auto-generated catch block  
  27.             e.printStackTrace();  
  28.         }  
  29.         result=(int) (Math.random()*10000);  
  30.         System.out.println(this.getName()+"計算結束:...");  
  31.     }  
  32. }  

觀察輸出結果,發現主線程並沒有等待計算線程執行結束,就輸出結果了。

Java代碼  收藏代碼
  1. 主線程執行  
  2. result:0  
  3. 計算線程開始計算:...  
  4. 1990毫秒後:0  
  5. 計算線程計算結束:...  

3、使用中斷(Interrupt)取消線程

已經啓動的線程是活躍的,即isAlive()方法返回trur,線程終止之前一直是活躍的。有三種方法可以使線程終止:

1)run()方法正常返回;

2)run()方法以外結束;

3)應用程序終止。

      經常會碰到這樣的情況,我們創建了執行某項工作的線程,然後再它完成之前需要取消這樣工作。要使線程在完成任務之前可取消,必須採取一定的措施,但應該是一個清晰而安全的機制使線程終止。我們可以通過中斷(Thread.interrupt)線程來請求取消,並且讓線程來監視並響應中斷。中斷請求通常是用戶希望能夠終止線程的執行,但並不會強制終止線程,但是它會中斷線程的睡眠狀態,比如調用sleep和wait方法後。線程自己檢查中斷狀態並終止線程比直接調用stop方法要安全很多,因爲線程可以保存自己的狀態。並且stop()方法已經不推薦使用了。

和中斷線程有關的方法有:

1)interrupt,向線程發送中斷;

2)isInterrupted,測試線程是否已經被中斷;

3)Interrupt,測試線程是否已經被中斷,隨後清除線程的“中斷”狀態。

      線程的中斷狀態只有線程自己清除,當線程偵測到自己被中斷時,經常需要在響應中斷之前做某些清除工作,這些清除工作可能涉及那些在線程仍然保持中斷狀態時會受到影響的操作。如果被中斷的線程正在執行sleep,或者wait方法,就會拋出InterruptException異常。這種拋出異常的中斷會清除線程的中斷狀態。大體上任何執行阻塞操作的方法,都應該通過Interrupt來取消阻塞操作。

     下面的程序,主線程在等待計算線程2000毫秒後,中斷計算線程,計算線程由於正在執行sleep,就會拋出InterruptException異常,終止休眠狀態,然後進入異常處理,在catch中可以做一些清理工作(如果需要),然後線程執行結束。

這是一種典型的終止線程執行的方法:

Java代碼  收藏代碼
  1. public class TestInterrupt extends Thread {  
  2.   
  3.     static int result=0;  
  4.     public TestInterrupt(String name){  
  5.         super(name);  
  6.     }  
  7.     public static void main(String[] args) {  
  8.         System.out.println("主線程執行");  
  9.         Thread t=new TestInterrupt("計算線程");  
  10.         t.start();  
  11.         System.out.println("result:"+result);  
  12.         try {  
  13.             long start=System.nanoTime();  
  14.             t.join(2000);  
  15.             long end=System.nanoTime();  
  16.             t.interrupt();  
  17.             System.out.println((end-start)/1000000+"毫秒後:"+result);  
  18.         } catch (Exception e) {  
  19.             e.printStackTrace();  
  20.         }  
  21.     }  
  22.     @Override  
  23.     public void run() {  
  24.         System.out.println(this.getName()+"開始計算:...");  
  25.         try {  
  26.             Thread.sleep(4000);  
  27.         } catch (InterruptedException e) {  
  28.             System.out.println(this.getName()+"被中斷,結束");  
  29.             return;  
  30.         }  
  31.         result=(int) (Math.random()*10000);  
  32.         System.out.println(this.getName()+"計算結束:...");  
  33.     }  
  34. }  

 輸出結果如下:

Java代碼  收藏代碼
  1. 主線程執行  
  2. result:0  
  3. 計算線程開始計算:...  
  4. 1991毫秒後:0  
  5. 計算線程被中斷,結束  

    從輸出結果中可以看出,計算線程被中斷後,run()方法中的最後兩行語句沒有執行。沒有產生計算結果。

t.interrupt()不會中斷正在執行的線程,只是將線程的標誌位設置成true。但是如果線程在調用sleep(),join(),wait()方法時線程被中斷,則這些方法會拋出InterruptedException,在catch塊中捕獲到這個異常時,線程的中斷標誌位已經被設置成false了,因此在此catch塊中調用t.isInterrupted(),Thread.interrupted()始終都爲false。

如果一個線程長時間沒有調用能夠拋出InterruptException異常的方法,那麼線程就必須定期的調用Thread.interrupted方法,如果接收到中斷就返回true,然後就可以退出線程。

Java代碼  收藏代碼
  1. public class TestInterrupt extends Thread {  
  2.   
  3.     static int result=0;  
  4.     public TestInterrupt(String name){  
  5.         super(name);  
  6.     }  
  7.     public static void main(String[] args) {  
  8.         System.out.println("主線程執行");  
  9.         Thread t=new TestInterrupt("計算線程");  
  10.         t.start();  
  11.         System.out.println("result:"+result);  
  12.         try {  
  13.             long start=System.nanoTime();  
  14.             t.join(10);  
  15.             long end=System.nanoTime();  
  16.             t.interrupt();  
  17.             System.out.println((end-start)/1000000+"毫秒後:"+result);  
  18.         } catch (Exception e) {  
  19.             e.printStackTrace();  
  20.         }  
  21.     }  
  22.     @Override  
  23.     public void run() {  
  24.         System.out.println(this.getName()+"開始計算...");  
  25.         for(int i=0;i<100000;i++){  
  26.             result++;  
  27.             if(Thread.interrupted()){  
  28.                 System.out.println(this.getName()+"被中斷");  
  29.                 return;  
  30.             }  
  31.         }  
  32.         System.out.println(this.getName()+"計算結束...");  
  33.     }  
  34. }  

 輸出結果如下:

Java代碼  收藏代碼
  1. result:0  
  2. 計算線程開始計算...  
  3. 計算線程被中斷  
  4. 7毫秒後:18563  
  5. false  

 上面的程序,計算線程原計劃執行100000次循環,主線程等待10毫秒後,中斷計算線程,計算線程接收到中斷後,就可以結束執行了。在更加複雜的應用程序中,當線程收到中斷信號後,拋出InterruptException異常更有意義。把中斷處理代碼集中到catch字句中。

4、使用stop終止線程

      在Thread類中提供了Stop方法,來強迫線程停止執行。但是現在已經過時了。

      該方法具有固定的不安全性。用Thread.Stop來終止線程將釋放它已經鎖定的所有監視器(作爲沿堆棧向上傳播的未檢查     ThreadDeath異常的一個自然後果)。如果以前受這些監視器保護的任何對象都處於一種不一致的狀態,則損壞的對象將對其他線程可見,這有可能導致任意的行爲。Stop的許多使用方式都應由只修改某些變量以知識目標線程應該停止運行的代碼來取代。目標線程應定期檢查該變量,並且如果該變量指示它要停止運行,則從其運行方法依次返回。如果目標線程等待很長時間(例如基於一個條件變量),則應使用interrupt方法來中斷該等待。

     無論該線程在做些什麼,它所代表的線程都被迫異常停止,並拋出一個新創建的ThreadDeath對象作爲異常。停止一個尚未啓動的線程是允許的。如果最後啓動了該線程,它會立即終止。

     應用程序通常不應視圖捕獲ThreadDeath,除非它必須執行某些異常的清楚操作(注意,拋出ThreadDeath將導致try語句的finally字句在線程終止前執行)。如果catch字句捕獲了一個ThreadDeath對象,則重新拋出該對象很重要,因爲這樣該線程纔會真正終止。

     對其它爲捕獲的異常做出反應的頂級錯誤處理不會打印輸出消息,或者另外通知應用程序爲捕獲到的已換成那個是否爲ThreadDeath的一個實例。

5、結束程序的執行

      每個應用程序都從執行main的線程開始的,如果應用程序沒有創建任何其它的線程,那麼main方法返回時,應用程序就結束了,但是如果應用程序創建了其它線程,就要根據線程的類型分情況來考慮了。

      線程一般分爲兩種:用戶線程和守護線程。用戶線程的存在可以使應用程序保持運行狀態,而守護線程則不會。當最後一個用戶線程結束時,所有守護線程都會被終止,應用程序也隨之結束。守護線程的終止,很像調用destroy所產生的終止,事發突然,沒有幾乎做任何清除,所以應該考慮清楚,用守護線程執行哪種類型的任務。使用Thread.setDaemon(true)可以把線程標記爲守護線程。默認情況下,線程的守護狀態繼承自創建它的線程。

     一般main線程是程序運行時第一個啓動的線程,稱作爲初始線程。如果希望應用程序在初始線程消亡後就退出,就可以把所創建處理的線程都標記爲守護線程。我們也可以通過調用System,或者Runtime的exit方法來強制應用程序結束,這個方法將終止Java虛擬機的當前執行過程。

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