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类中包含三个与中断状态相关的方法:

interrupt方法试图中断线程并设置中断状态标志变量为true;

isInterrupted方法测试线程是否已经中断,返回中断状态变量的值;

interrupted方法用于清除线程的中断状态,并返回之前的值,即如果当前线程是中断状态,则重新设置为false,并且返回true;

中断一个线程常用的办法即通过调用interrupt方法试图中断该线程,线程中执行的任务收到中断请求后会选择一个合适的时机结束线程。需要注意的是通常由线程的所有者来从外部中断线程的执行,因为通常只有线程的所有者知道在满足某种条件时可以请求中断线程。

3. 阻塞方法与线程的中断

阻塞方法会使线程进入阻塞的状态,例如:等待获得一个锁、Thread.sleep方法、BlockingQueue的put、take方法等。

大部分的阻塞方法都是响应中断的,即这些方法在线程中执行时如果发现线程被中断,会清除线程的中断状态,并抛出InterruptedException表示该方法的执行过程被从外部中断。响应中断的阻塞方法通常会在入口处先检查线程的中断状态,线程不是中断状态时,才会继续执行。

根据阻塞方法的特性,我们就可以利用中断的机制来结束包含阻塞方法的任务的执行:

public MyTask implements Runnable
{
   public void run()
   {
       try
       {
          while(!Thread.currentThread().isInterrupted())
          {
               Thread.sleep(3000);  //阻塞方法
               //...其他操作
          }
        }
       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);
                 //...其他操作
              }
              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);     //取消任务的执行
       }
    }
}
时间: 2024-10-11 09:09:03

Java并发编程(五) 任务的取消的相关文章

《Java并发编程实战》笔记-取消与关闭

1,中断是实现取消的最合理方式.2,对中断操作的正确理解是:它并不会真正地中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一个合适的时刻中断自己.3,区分任务和线程对中断的反应是很重要的4,线程有一个相应的所有者,即创建该线程的类.5,除非拥有某个线程,否则不能对该线程进行操控.由于每个线程拥有各自的中断策略,因此除非你知道中断对该线程的含义,否则就不应该中断这个线程.

《Java并发编程实战》第十五章 原子变量与非阻塞同步机制 读书笔记

一.锁的劣势 锁定后如果未释放,再次请求锁时会造成阻塞,多线程调度通常遇到阻塞会进行上下文切换,造成更多的开销. 在挂起与恢复线程等过程中存在着很大的开销,并且通常存在着较长时间的中断. 锁可能导致优先级反转,即使较高优先级的线程可以抢先执行,但仍然需要等待锁被释放,从而导致它的优先级会降至低优先级线程的级别. 二.硬件对并发的支持 处理器填写了一些特殊指令,例如:比较并交换.关联加载/条件存储. 1 比较并交换 CAS的含义是:"我认为V的值应该为A,如果是,那么将V的值更新为B,否则不需要修

java并发编程6.取消与关闭

如果外部代码能在某个操作正常完成之前将其置入"完成"状态,那么这个操作就可以称为可取消的. Java没有提供任何机制来安全地终止线程.但它提供了中断,这是一种协作机制,能够使一个线程终止另一个线程的当前工作. 其中一种协作机制能设置某个"已请求取消"的标志,而任务将定期地查看该标志,如果设置了这个标志,那么任务将提前结束. 自定义取消机制 /** * 素数生成器 */ private class PrimeGenerator implements Runnable{

Java 并发编程之任务取消(九)

Jvm关闭 jvm可正常关闭也可强行关闭,正常关闭有多种触发方式: 当最后一个正常(非守护,下面会讲到什么是守护线程)线程结束时 当调用system.exit时,或者通过其他特定于平台的方法关闭时(例如发送了SIGINT信号或键入Ctrl-c) 通过其他特定平台的方法关闭jvm,调用Runtime.halt或者在操作系统当中杀死JVM进程(例如发送sigkill)来强行关闭jvm. 关闭钩子 在正常关闭中,jvm首先调用所有已注册的关闭钩子,关闭钩子是指通过 Runtime.addShutdow

Java 并发编程之任务取消(八)

处理非正常的线程中止 当单线程的控制台程序由于 发生了一个未捕获的异常而终止时,程序将停止运行,并产生与程序正常输出非常不同的栈追踪信息,这种情况是很容易理解的.然而,如果并发程序中的某个线程发生故障,那么通常不会如此明显.在控制台中可能会输出栈追踪信息,但没有人会观察控制台.此外,当线程发生故障时,应用程序可能看起来仍然 在工作,所以这个失败很可能被忽略.下面要讲的问题就是监测并防止在程序中"遗漏"线程的方法 . 导致线程提前死亡的最主要原因就是RuntimeException. 我

转: 【Java并发编程】之十八:第五篇中volatile意外问题的正确分析解答(含代码)

转载请注明出处:http://blog.csdn.net/ns_code/article/details/17382679 在<Java并发编程学习笔记之五:volatile变量修饰符-意料之外的问题>一文中遗留了一个问题,就是volatile只修饰了missedIt变量,而没修饰value变量,但是在线程读取value的值的时候,也读到的是最新的数据.但是在网上查了很多资料都无果,看来很多人对volatile的规则并不是太清晰,或者说只停留在很表面的层次,一知半解. 这两天看<深入Ja

[Java 并发] Java并发编程实践 思维导图 - 第五章 基础构建模块

根据<Java并发编程实践>一书整理的思维导图.希望能够有所帮助. 第一部分: 第二部分:

Java并发编程(五)ConcurrentHashMap的实现原理和源码分析

相关文章 Java并发编程(一)线程定义.状态和属性 Java并发编程(二)同步 Java并发编程(三)volatile域 Java并发编程(四)Java内存模型 前言 在Java1.5中,并发编程大师Doug Lea给我们带来了concurrent包,而该包中提供的ConcurrentHashMap是线程安全并且高效的HashMap,本节我们就来研究下ConcurrentHashMap是如何保证线程安全的同时又能高效的操作. 1.为何用ConcurrentHashMap 在并发编程中使用Has

《Java并发编程实战》要点笔记及java.util.concurrent 的结构介绍

买了<java并发编程实战>这本书,看了好几遍都不是很懂,这个还是要在实战中找取其中的要点的,后面看到一篇文章笔记做的很不错分享给大家!! 原文地址:http://blog.csdn.net/cdl2008sky/article/details/26377433 Subsections  1.线程安全(Thread safety) 2.锁(lock) 3.共享对象 4.对象组合 5.基础构建模块 6.任务执行 7.取消和关闭 8.线程池的使用 9.性能与可伸缩性 10.并发程序的测试 11.显