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

  • 相关阅读:
    【POJ 1958】 Strange Towers of Hanoi
    【HNOI 2003】 激光炸弹
    【POJ 3263】 Tallest Cow
    【POJ 2689】 Prime Distance
    【POJ 2777】 Count Color
    【POJ 1995】 Raising Modulo Numbers
    【POJ 1845】 Sumdiv
    6月16日省中集训题解
    【TJOI 2018】数学计算
    【POJ 1275】 Cashier Employment
  • 原文地址:https://www.cnblogs.com/longfurcat/p/9582413.html
Copyright © 2011-2022 走看看