Java併發編程(五) 任務的取消

  在Java中沒法搶佔式地中止一個任務的執行,而是經過中斷機制實現了一種協做式的方式來取消任務的執行。外部程序只能向一個線程發送中斷請求,而後由任務本身負責在某個合適的時刻結束執行。java

1. 設置取消標誌編程

  這是最基本也是最簡單的中止一個任務執行的辦法,即設置一個取消任務執行的標誌變量,而後反覆檢測該標誌變量的值。安全

public class MyTask implements Runnable
{ 
   private volatile  running = true;

   public void run()
   {
        while(running)
        {
            //...操做         
        }
   }

  public  void stop()
  {
      running = false;
  }
}

  一般須要使用volatile關鍵字來修飾標誌變量,以保證該任務類是線程安全的。可是,若是run方法中存在阻塞的操做,則該任務可能永遠也沒法正常退出。併發

2. 中斷線程的執行框架

 每一個線程都有一個boolean類型的變量來標誌該線程的中斷狀態,Thread類中包含三個與中斷狀態相關的方法:socket

interrupt方法試圖中斷線程並設置中斷狀態標誌變量爲true;測試

isInterrupted方法測試線程是否已經中斷,返回中斷狀態變量的值;this

interrupted方法用於清除線程的中斷狀態,並返回以前的值,即若是當前線程是中斷狀態,則從新設置爲false,而且返回true;spa

 中斷一個線程經常使用的辦法即經過調用interrupt方法試圖中斷該線程,線程中執行的任務收到中斷請求後會選擇一個合適的時機結束線程。須要注意的是一般由線程的全部者來從外部中斷線程的執行,由於一般只有線程的全部者知道在知足某種條件時能夠請求中斷線程。線程

3. 阻塞方法與線程的中斷

 阻塞方法會使線程進入阻塞的狀態,例如:等待得到一個鎖、Thread.sleep方法、BlockingQueue的put、take方法等。

 大部分的阻塞方法都是響應中斷的,即這些方法在線程中執行時若是發現線程被中斷,會清除線程的中斷狀態,並拋出InterruptedException表示該方法的執行過程被從外部中斷。響應中斷的阻塞方法一般會在入口處先檢查線程的中斷狀態,線程不是中斷狀態時,纔會繼續執行。

 根據阻塞方法的特性,咱們就能夠利用中斷的機制來結束包含阻塞方法的任務的執行:

public MyTask implements Runnable
{
   public void run()
   { 
       try
       {
          while(!Thread.currentThread().isInterrupted())
          {
               Thread.sleep(3000);  //阻塞方法
               //...其餘操做
return; } } catch(InterruptedException ex) { Thread.currentThread().interrupt(); //恢復中斷狀態 } } } public class Test { public void method() { Thread thread = new Thread(new MyTask()).start(); //.... thread.interrupt(); //經過中斷機制請求結束線程的執行 } }

  在上面例子中,在線程的任務中包含了阻塞方法sleep,在線程外部經過interrupt方法請求結束線程的執行,sleep方法在檢測到線程處於中斷狀態時,會清除線程的中斷狀態並拋出InterruptedException。對於阻塞方法拋出的InterruptedException,一般有兩種處理方法:

  第一種是從新拋出InterruptedException,將該異常的處理權交給方法調用者,這樣該方法也成爲了阻塞方法(調用了阻塞方法而且拋出InterruptedException);

  第二種是經過interrupt方法恢復線程的中斷狀態,這樣可使得處理該線程的其餘代碼可以檢測到線程的中斷狀態;

 上面的例子採用的是第二種方法,由於阻塞方法是在Runnable接口的run方法中執行的,並無其餘客戶方法直接調用Runnable的run方法,所以沒有接收InterruptedException的調用者。

  對於不支持取消但仍然調用了響應中斷的阻塞方法的任務,應該先在本地保存中斷狀態,而後在任務結束時恢復中斷狀態,而不是在捕獲InterrruptedException時就恢復中斷狀態:

public  class  MyTask implements Runnable
{
     boolean interrupted = false;

     public void run()
     { 
        try
        {
           while(true)           //不支持取消操做
           {
              try
              {
                 Thread.sleep(3000);   
                 //...其餘操做
return; } catch(InterruptedException ex) { interrupted = true; //在本地保存中斷狀態
//Thread.currentThread().interrupt(); //不要在這兒當即恢復中斷
} } } finally { if(interrupted) Thread.currentThread().interrupt(); //恢復中斷 } } }

   在上面的例子中,因爲大部分響應中斷阻塞方法都會在方法的入口處檢查線程的中斷狀態,若是在捕獲InterruptedException的地方當即恢復中斷,則可能致使剛恢復的中斷狀態被阻塞方法的入口處被檢測到,從而又再次拋出InterruptedException,這樣可能致使程序陷入死循環。

   也有一些阻塞方法是不響應中斷的,即在收到中斷請求時不會拋出InterruptedException,如:java.io包中的Socket I/O方法、java.nio.channels包中的InterruptibleChannel類的相關阻塞方法、java.nio.channels包中的Selector類的select方法等。

   若是線程執行的任務中包含這類不響應中斷的方法,則沒法經過標準的中斷機制來結束任務的運行,但仍然有其餘辦法。如:

對於java.io包的Socket I/O方法,能夠經過關閉套接字,從而使得read或者write方法拋出SocketException而跳出阻塞;

java.nio.channels包中的InterruptibleChannel類的方法實際上是響應線程的interrupt方法的,只是拋出的不是InterruptedException,而是ClosedByInterruptedException,除此以外,也能夠經過調用InterruptibleChannel的close方法來使線程跳出阻塞方法,並拋出AsynchronousClosedException;

對於java.nio.channels包的Selector類的select方法,能夠經過調用Selector類的close方法或者wakeup方法從而拋出ClosedSelectorExeception;

   能夠經過改寫Thread類的interrupt方法從而將非標準的中斷線程的機制封裝在Thread中,以中斷包含Socket I/O的任務爲例:

public class ReadThread extends Thread
{
     private final  Socket client;
     private final  InputStream in;
     
     public ReadThread(Socket client) throws IOException 
     {
         this.client = client;
         in = client.getInputStream();
     }

     public void interrupt()
    {
        try
        {
             socket.close();
        }
        catch(IOException ignore){}
        finally
        {
            super.interrupt();
        }
    }

    public void run()
    {
        //調用in.read方法
    }

}

4. 經過Future來取消任務的執行

  Future接口有一個cancel方法,能夠經過該方法取消任務的執行,cancel方法有一個boolean型的參數mayInterruptIfRunning。

若是設置爲false,對於正在執行的任務只能等到任務執行完畢,沒法中斷;

若是設置爲true,對於正在執行的任務能夠試圖中斷任務的運行,這種狀況一般只在與Executor框架配合時使用,由於執行任務的線程是由Executor建立的,Executor知道該如何中斷執行任務的線程;

  

puhblic class Test
{
    private Executor  executor = Executors.newSingleThreadExecutor();
    
     public static void timedRun(Runnable runnable,long timeout,TimeUnit unit) throws InterruptedException
    {
       try
       {
            Future<?>  task = executor.submit(runnable);
            task.get(timeout,unit);   //任務最多運行指定的時間
       }
       catch(TimeoutException e1){}
       catch(ExecutionException e2)
       {
            throw e2.getCause();
       }
       finally
       {
           task.cancel(true);     //取消任務的執行
       }
    }
}

 

 

 

參考資料 《Java併發編程實戰》

相關文章
相關標籤/搜索