解开Future的神秘面纱之取消任务 在之前写过的一篇随笔中已经提到了Future的应用场景和特性。

(ExecutorService—— Future submit(Callable task)) 我们先来回顾一下: 复制代码 public class FutureCancelDemo { public static void main(String[] args) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); Future future = exec.submit(new DemoTask()); TimeUnit.SECONDS.sleep(2); //给足时间让启动起来,但又不足以让其完成 future.cancel(true); } } class Target { //任务目标 } class DemoTask implements Callable { //任务 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 future = exec.submit(new DemoTask()); TimeUnit.SECONDS.sleep(2); //给足时间让启动起来,但又不足以让其完成 boolean cancelResult1 = future.cancel(true); //true表示,如果已经运行,则中断 Future 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操作的定义:https://www.cnblogs.com/longfurcat/p/9582413.html
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信