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并发编程实战》

相关文章
相关标签/搜索