zoukankan      html  css  js  c++  java
  • 揭开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())的。

       

    由上我们知道,取消操作与一个变量mayInterruptIfRunning有很大关联。由以上代码可知,

    mayInterruptIfRunning为true时

      (1)设置任务状态为INTERRUPTING

      (2)中断执行任务的线程

      (3)若中断成功,设置任务状态为INTERRUPTED

      (4)执行finishCompletion() => 此方法与等待线程有关。请看结果获取

    mayInterruptIfRunning为false时

      (1)设置任务状态为CANCELLED

      (2)执行finishCompletion() 同上

    我们来看看cancel方法的定义。

      

    说的就是,如果任务已经开始被线程执行,那么mayInterruptIfRunning决定的是,在任务执行过程中,要不要通过中断其执行线程来尝试打断任务。

    任务取消≠任务不再运行

    熟悉线程中断的都知道,它是一种协作机制,如果任务代码中没有对中断信号进行响应,那么程序就会继续执行下去。这是中断的情况,不中断自然肯定不能打断任务的执行。

    那么问题就来了,这个取消操作到底有什么意义呢?

    意义就是,如果任务已被标识为已取消,那么获取其结果的线程,就不需要等待其完成,抛出已取消异常。事实上任务其实还在跑。

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

    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();
            }
        }
    
    }

    运行结果:

    很明显,程序中,在任务2(Task[1])在其执行完成之前,已经被我们"取消"了。但是从控制台输出可知,它其实还在跑,并在完成的时候输出"Task[1] completed!"。然后我们尝试获取任务2(Task[1])的结果,发现其抛出了已取消异常。

    cancel方法返回值的含义

    一切都跟前面符合前面的说明,但是细心的朋友可能发现一件奇怪的事情。那就是为什么不管是cancel(false)还是cancel(true)返回值都是true?

    我们来看看官方的定义:

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

    其他任何情况都会返回true

  • 相关阅读:
    javascript DOM节点(一)
    [转]php初级教程(七)一个新闻管理系统(准备工作)
    [转]php初级教程(九)添加新闻内容程序的编写
    [转]php初级教程(六)php表单处理文件上传
    [转]php初级教程(一)php平台的搭建
    [转]php初级教程(三)php的常用函数和基本流程(20071217 14:46:16)[编辑][删除]
    [转]php初级教程(八)基本php配置文件的编写
    [转]php初级教程(十一)用户的注册
    [转]php初级教程(四)相关环境变量和服务器变量的获取
    [转]php初级教程(五)php表单处理
  • 原文地址:https://www.cnblogs.com/longfurcat/p/9582413.html
Copyright © 2011-2022 走看看