ballbet网站ballbet网站

ballbet体育
betboy贝博手机版

解开Future的神秘面纱之取消任务

在之前写过的一篇随笔中已经提到了Future的应用场景和特性。(ExecutorService——<T> Future<T> submit(Callable<T> task))

我们先来回顾一下:

public class FutureCancelDemo { public static void main(String[] args) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); Future<Target> future = exec.submit(new DemoTask()); TimeUnit.SECONDS.sleep(2); //给足时间让启动起来,但又不足以让其完成 future.cancel(true); }}class Target { //任务目标}class DemoTask implements Callable<Target> { //任务 private static int counter = 0; private final int id = counter++; @Override public Target call() throws Exception { System.out.println(this+ " start..."); TimeUnit.SECONDS.sleep(5); //模拟任务运行需要的时间 System.out.println(this + " completed!"); return new Target(); } @Override public String toString() { return "Task[" + id + "]"; }}

 

一般情况下,我们会在哪里用到Future对象呢?

  就是当我们需要控制任务(Runnable/Callable对象)的时候,我们把任务提交给执行器(ExecutorService.submit()),并返回一个控制句柄(Future)。以便在未来的某个时刻检查任务执行状态、获取任务执行结果、以及在必要的时候取消任务等。

今天我们就来看看,Future是如何取消任务的。

 

我们知道Future只是一个接口,它到底是如何实现任务取消的呢?

  我们知道,把任务提交给执行器,执行器返回给我们一个Future。但是由于代码封装得很好,Future和ExecutorService都只是一个接口,我们只知道怎么用,却不知道其内部是如何实现的。如果要查看它到底是如何实现的就要追根溯源的查,直到找到最终的实现类。首先,我们的ExecutorService是用工厂类Executors获得的。就拿上述代码为例,我们获得了一个缓冲线程池ThreadPoolExecutor类型的对象,而ThreadPoolExecutor并没有重写submit方法,而是延用它父类的实现,而它父类便是AbstractExecutorSevice。这个类提供了ExecutorService接口最基本的底层实现。我们终于找到了submit方法的实现。

从这里我们可以看到,该方法以RunnableFuture作为返回值。且该值由newTaskFor方法生成。

然而这RunnableFuture,FutureTask,Future到底是何关系呢?

即RunnableFuture继承了Runnable及Future接口,表示一个可控制的任务。而FutureTask实现了这个接口。故Future的取消操作最终由这个FutureTask实现。

我们来看看它是如何实现取消操作(future.cancel())的。

 

很明显,如果任务已经启动,则取消任务的方法就是中断执行它的线程

说到这里,可能有人会对cancel的参数mayInterruptIfRunning产生疑惑,这到底是用来干什么的。我们来看看,Future对该方法的定义。

也就是说,如果mayInterruptIfRunning为true,则如果任务未启动,则修改任务状态标识,使得该任务无法启动。如果已经启动,则可以采用中断线程的策略结束任务。即未启动和已启动的任务都能取消。

如果mayInterruptIfRunning为false,则只能取消未启动的任务。已启动的任务会任由它继续执行。

 

通过一个例子加深一下印象:

public class FutureCancelDemo2 { public static void main(String[] args) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); //缓冲线程池 Future<Target> future = exec.submit(new DemoTask()); TimeUnit.SECONDS.sleep(2); //给足时间让启动起来,但又不足以让其完成 boolean cancelResult1 = future.cancel(true); //true表示,如果已经运行,则中断 Future<Target> future2 = exec.submit(new DemoTask()); TimeUnit.SECONDS.sleep(2); boolean cancelResult2 = future2.cancel(false); System.out.println("cancelResult1:" + cancelResult1); System.out.println("cancelResult2:" + cancelResult2); try { Target target = future2.get(); } catch (ExecutionException e) { e.printStackTrace(); } }}

 

运行结果:

对于上述代码及运行结果,是否感到奇怪。我来分享一下,我的疑惑之处吧。

①根据前面的定义,既然任务已经运行,那么cancel(false)应该不能中断任务才对,为何返回值会是true?

②由输出"Task[1] completed!"可知,任务2已经完成。为何future2.get()会报错?

解答:

我想当然的把cancel的返回值看作是取消的成功标志。我们来看一下Future接口的官方定义。

也就是说,cancel的返回值如果是false,则情况有这几种:任务已经完成,在这之前已经调用过一次cancel,其他原因导致无法取消。

其他任何情况都会返回true

另外由于cancel(false)不会实际上中断正在运行的任务,但是会实现逻辑上的取消,即修改任务执行标识为"已取消"。故再调用get方法自然无意义。

我们来看看get操作的定义:

 

, 1, 0, 9);

欢迎阅读本文章: 陈永康

ballbet体育直播

ballbet体育