【Java併發編程】之三:線程掛起、恢復與終止的正確方法(含代碼)

掛起和恢復線程

    Thread 的API中包含兩個被淘汰的方法,它們用於臨時掛起和重啓某個線程,這些方法已經被淘汰,因爲它們是不安全的,不穩定的。如果在不合適的時候掛起線程(比如,鎖定共享資源時),此時便可能會發生死鎖條件——其他線程在等待該線程釋放鎖,但該線程卻被掛起了,便會發生死鎖。另外,在長時間計算期間掛起線程也可能導致問題。

    下面的代碼演示了通過休眠來延緩運行,模擬長時間運行的情況,使線程更可能在不適當的時候被掛起:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public class DeprecatedSuspendResume extends Object implements Runnable{  
  2.   
  3.     //volatile關鍵字,表示該變量可能在被一個線程使用的同時,被另一個線程修改  
  4.     private volatile int firstVal;  
  5.     private volatile int secondVal;  
  6.   
  7.     //判斷二者是否相等  
  8.     public boolean areValuesEqual(){  
  9.         return ( firstVal == secondVal);  
  10.     }  
  11.   
  12.     public void run() {  
  13.         try{  
  14.             firstVal = 0;  
  15.             secondVal = 0;  
  16.             workMethod();  
  17.         }catch(InterruptedException x){  
  18.             System.out.println("interrupted while in workMethod()");  
  19.         }  
  20.     }  
  21.   
  22.     private void workMethod() throws InterruptedException {  
  23.         int val = 1;  
  24.         while (true){  
  25.             stepOne(val);  
  26.             stepTwo(val);  
  27.             val++;  
  28.             Thread.sleep(200);  //再次循環錢休眠200毫秒  
  29.         }  
  30.     }  
  31.       
  32.     //賦值後,休眠300毫秒,從而使線程有機會在stepOne操作和stepTwo操作之間被掛起  
  33.     private void stepOne(int newVal) throws InterruptedException{  
  34.         firstVal = newVal;  
  35.         Thread.sleep(300);  //模擬長時間運行的情況  
  36.     }  
  37.   
  38.     private void stepTwo(int newVal){  
  39.         secondVal = newVal;  
  40.     }  
  41.   
  42.     public static void main(String[] args){  
  43.         DeprecatedSuspendResume dsr = new DeprecatedSuspendResume();  
  44.         Thread t = new Thread(dsr);  
  45.         t.start();  
  46.   
  47.         //休眠1秒,讓其他線程有機會獲得執行  
  48.         try {  
  49.             Thread.sleep(1000);}   
  50.         catch(InterruptedException x){}  
  51.         for (int i = 0; i < 10; i++){  
  52.             //掛起線程  
  53.             t.suspend();  
  54.             System.out.println("dsr.areValuesEqual()=" + dsr.areValuesEqual());  
  55.             //恢復線程  
  56.             t.resume();  
  57.             try{   
  58.                 //線程隨機休眠0~2秒  
  59.                 Thread.sleep((long)(Math.random()*2000.0));  
  60.             }catch(InterruptedException x){  
  61.                 //略  
  62.             }  
  63.         }  
  64.         System.exit(0); //中斷應用程序  
  65.     }  
  66. }  
    某次運行結果如下:

  

    從areValuesEqual()返回的值有時爲true,有時爲false。以上代碼中,在設置firstVal之後,但在設置secondVal之前,掛起新線程會產生麻煩,此時輸出的結果會爲false(情況1),這段時間不適宜掛起線程,但因爲線程不能控制何時調用它的suspend方法,所以這種情況是不可避免的。

    當然,即使線程不被掛起(註釋掉掛起和恢復線程的兩行代碼),如果在main線程中執行asr.areValuesEqual()進行比較時,恰逢stepOne操作執行完,而stepTwo操作還沒執行,那麼得到的結果同樣可能是false(情況2)


     下面我們給出不用上述兩個方法來實現線程掛起和恢復的策略——設置標誌位。通過該方法實現線程的掛起和恢復有一個很好的地方,就是可以在線程的指定位置實現線程的掛起和恢復,而不用擔心其不確定性。  

     對於上述代碼的改進代碼如下:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public class AlternateSuspendResume extends Object implements Runnable {  
  2.   
  3.     private volatile int firstVal;  
  4.     private volatile int secondVal;  
  5.     //增加標誌位,用來實現線程的掛起和恢復  
  6.     private volatile boolean suspended;  
  7.   
  8.     public boolean areValuesEqual() {  
  9.         return ( firstVal == secondVal );  
  10.     }  
  11.   
  12.     public void run() {  
  13.         try {  
  14.             suspended = false;  
  15.             firstVal = 0;  
  16.             secondVal = 0;  
  17.             workMethod();  
  18.         } catch ( InterruptedException x ) {  
  19.             System.out.println("interrupted while in workMethod()");  
  20.         }  
  21.     }  
  22.   
  23.     private void workMethod() throws InterruptedException {  
  24.         int val = 1;  
  25.   
  26.         while ( true ) {  
  27.             //僅當賢臣掛起時,才運行這行代碼  
  28.             waitWhileSuspended();   
  29.   
  30.             stepOne(val);  
  31.             stepTwo(val);  
  32.             val++;  
  33.   
  34.             //僅當線程掛起時,才運行這行代碼  
  35.             waitWhileSuspended();   
  36.   
  37.             Thread.sleep(200);    
  38.         }  
  39.     }  
  40.   
  41.     private void stepOne(int newVal)   
  42.                     throws InterruptedException {  
  43.   
  44.         firstVal = newVal;  
  45.         Thread.sleep(300);    
  46.     }  
  47.   
  48.     private void stepTwo(int newVal) {  
  49.         secondVal = newVal;  
  50.     }  
  51.   
  52.     public void suspendRequest() {  
  53.         suspended = true;  
  54.     }  
  55.   
  56.     public void resumeRequest() {  
  57.         suspended = false;  
  58.     }  
  59.   
  60.     private void waitWhileSuspended()   
  61.                 throws InterruptedException {  
  62.   
  63.         //這是一個“繁忙等待”技術的示例。  
  64.         //它是非等待條件改變的最佳途徑,因爲它會不斷請求處理器週期地執行檢查,   
  65.         //更佳的技術是:使用Java的內置“通知-等待”機制  
  66.         while ( suspended ) {  
  67.             Thread.sleep(200);  
  68.         }  
  69.     }  
  70.   
  71.     public static void main(String[] args) {  
  72.         AlternateSuspendResume asr =   
  73.                 new AlternateSuspendResume();  
  74.   
  75.         Thread t = new Thread(asr);  
  76.         t.start();  
  77.   
  78.         //休眠1秒,讓其他線程有機會獲得執行  
  79.         try { Thread.sleep(1000); }   
  80.         catch ( InterruptedException x ) { }  
  81.   
  82.         for ( int i = 0; i < 10; i++ ) {  
  83.             asr.suspendRequest();  
  84.   
  85.             //讓線程有機會注意到掛起請求  
  86.             //注意:這裏休眠時間一定要大於  
  87.             //stepOne操作對firstVal賦值後的休眠時間,即300ms,  
  88.             //目的是爲了防止在執行asr.areValuesEqual()進行比較時,  
  89.             //恰逢stepOne操作執行完,而stepTwo操作還沒執行  
  90.             try { Thread.sleep(350); }   
  91.             catch ( InterruptedException x ) { }  
  92.   
  93.             System.out.println("dsr.areValuesEqual()=" +   
  94.                     asr.areValuesEqual());  
  95.   
  96.             asr.resumeRequest();  
  97.   
  98.             try {   
  99.                 //線程隨機休眠0~2秒  
  100.                 Thread.sleep(  
  101.                         ( long ) (Math.random() * 2000.0) );  
  102.             } catch ( InterruptedException x ) {  
  103.                 //略  
  104.             }  
  105.         }  
  106.   
  107.         System.exit(0); //退出應用程序  
  108.     }  
  109. }  
    運行結果如下:


    由結果可以看出,輸出的所有結果均爲true。首先,針對情況1(線程掛起的位置不確定),這裏確定了線程掛起的位置,不會出現線程在stepOne操作和stepTwo操作之間掛起的情況;針對情況2(main線程中執行asr.areValuesEqual()進行比較時,恰逢stepOne操作執行完,而stepTwo操作還沒執行),在發出掛起請求後,還沒有執行asr.areValuesEqual()操作前,讓main線程休眠450ms(>300ms),如果掛起請求發出時,新線程正執行到或即將執行到stepOne操作(如果在其前面的話,就會響應掛起請求,從而掛起線程),那麼在stepTwo操作執行前,main線程的休眠還沒結束,從而main線程休眠結束後執行asr.areValuesEqual()操作進行比較時,stepTwo操作已經執行完,因此也不會出現輸出結果爲false的情況。

    可以將ars.suspendRequest()代碼後的sleep代碼去掉,或將休眠時間改爲200(明顯小於300即可)後,查看執行結果,會發現結果中依然會有出現false的情況。如下圖所示:



   總結:線程的掛起和恢復實現的正確方法是:通過設置標誌位,讓線程在安全的位置掛起


終止線程

   當調用Thread的start()方法,執行完run()方法後,或在run()方法中return,線程便會自然消亡。另外Thread API中包含了一個stop()方法,可以突然終止線程。但它在JDK1.2後便被淘汰了,因爲它可能導致數據對象的崩潰。一個問題是,當線程終止時,很少有機會執行清理工作;另一個問題是,當在某個線程上調用stop()方法時,線程釋放它當前持有的所有鎖,持有這些鎖必定有某種合適的理由——也許是阻止其他線程訪問尚未處於一致性狀態的數據,突然釋放鎖可能使某些對象中的數據處於不一致狀態,而且不會出現數據可能崩潰的任何警告。

   終止線程的替代方法:同樣是使用標誌位,通過控制標誌位來終止線程。

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