当一个任务正在运行的过程当中,而咱们却发现这个任务已经没有必要继续运行了,那么咱们便产生了取消任务的须要。好比 上一篇文章 提到的线程池的 invokeAny
方法,它能够在线程池中运行一组任务,当其中任何一个任务完成时,invokeAny
方法便会中止阻塞并返回,同时也会 取消其余任务。那咱们如何取消一个正在运行的任务?java
前面两篇多线程的文章都有提到 Future<V>
接口和它的一个实现类 FutureTask<V>
,而且咱们已经知道 Future<V>
能够用来和已经提交的任务进行交互。Future<V>
接口定义了以下几个方法:segmentfault
get
方法:经过前面文章的介绍,咱们已经了解了 get
方法的使用 —— get
方法 用来返回和 Future
关联的任务的结果。带参数的 get
方法指定一个超时时间,在超时时间内该方法会阻塞当前线程,直到得到结果 。多线程
TimeoutException
异常;CancellationException
异常);ExecutionException
异常);InterruptedException
异常 —— 注意,当前线程是指调用 get
方法的线程,而不是运行任务的线程)。不带参数的 get
能够理解为超时时间无限大,即一直等待直到得到结果或者出现异常。ide
cancel(boolean mayInterruptIfRunning)
方法:该方法是非阻塞的。经过 JDK 的文档,咱们能够知道 该方法即可以用来(尝试)终止一个任务。测试
cancel
传入参数为 true
,那么便会去终止与 Future
关联的任务。cancel(false)
与 cancel(true)
的区别在于,cancel(false)
只 取消已经提交但尚未被运行的任务(即任务就不会被安排运行);而 cancel(true)
会取消全部已经提交的任务,包括 正在等待的 和 正在运行的 任务。this
isCancelled
方法:该方法是非阻塞的。在任务结束以前,若是任务被取消了,该方法返回 true
,不然返回 false
;若是任务已经完成,该方法则一直返回 false
。spa
isDone
方法:该方法一样是非阻塞的。若是任务已经结束(正常结束,或者被取消,或者执行出错),返回 true
,不然返回 false
。线程
而后咱们来实践下 Future
的 cancel
方法的功能:3d
import java.util.concurrent.*; public class FutureTest { public static void main(String[] args) throws Exception { ExecutorService threadPool = Executors.newSingleThreadExecutor(); SimpleTask task = new SimpleTask(3_000); // task 须要运行 3 秒 Future<Double> future = threadPool.submit(task); threadPool.shutdown(); // 发送关闭线程池的指令 double time = future.get(); System.out.format("任务运行时间: %.3f s\n", time); } private static final class SimpleTask implements Callable<Double> { private final int sleepTime; // ms public SimpleTask(int sleepTime) { this.sleepTime = sleepTime; } @Override public Double call() throws Exception { double begin = System.nanoTime(); Thread.sleep(sleepTime); double end = System.nanoTime(); double time = (end - begin) / 1E9; return time; // 返回任务运行的时间,以 秒 计 } } }
运行结果(任务正常运行):code
而后咱们定义一个用来取消任务的方法:
private static void cancelTask(final Future<?> future, final int delay) { Runnable cancellation = new Runnable() { @Override public void run() { try { Thread.sleep(delay); future.cancel(true); // 取消与 future 关联的正在运行的任务 } catch (InterruptedException ex) { ex.printStackTrace(System.err); } } }; new Thread(cancellation).start(); }
而后修改 main
方法:
public static void main(String[] args) { ExecutorService threadPool = Executors.newSingleThreadExecutor(); SimpleTask task = new SimpleTask(3_000); // task 须要运行 3 秒 Future<Double> future = threadPool.submit(task); threadPool.shutdown(); // 发送关闭线程池的指令 cancelTask(future, 2_000); // 在 2 秒以后取消该任务 try { double time = future.get(); System.out.format("任务运行时间: %.3f s\n", time); } catch (CancellationException ex) { System.err.println("任务被取消"); } catch (InterruptedException ex) { System.err.println("当前线程被中断"); } catch (ExecutionException ex) { System.err.println("任务执行出错"); } }
运行结果:
能够看到,当任务被取消时,Future
的 get
方法抛出了 CancellationException
异常,而且成功的取消了任务(从构建(运行)总时间能够发现)。
这样就能够了吗?调用 Future
的 cancel(true)
就必定能取消正在运行的任务吗?
咱们来写一个真正的耗时任务,判断一个数是否为素数,测试数据为 1000000033 (它是一个素数)。
import java.util.concurrent.*; public class FutureTest { public static void main(String[] args) throws Exception { ExecutorService threadPool = Executors.newSingleThreadExecutor(); long num = 1000000033L; PrimerTask task = new PrimerTask(num); Future<Boolean> future = threadPool.submit(task); threadPool.shutdown(); boolean result = future.get(); System.out.format("%d 是否为素数? %b\n", num, result); } private static final class PrimerTask implements Callable<Boolean> { private final long num; public PrimerTask(long num) { this.num = num; } @Override public Boolean call() throws Exception { // i < num 让任务有足够的运行时间 for (long i = 2; i < num; i++) { if (num % i == 0) { return false; } } return true; } } }
在个人机器上,这个任务须要 13 秒才能运行完毕:
而后咱们修改 main
方法,在任务运行到 2 秒的时候调用 Future
的 cancel(true)
:
public static void main(String[] args) throws Exception { ExecutorService threadPool = Executors.newSingleThreadExecutor(); long num = 1000000033L; PrimerTask task = new PrimerTask(num); Future<Boolean> future = threadPool.submit(task); threadPool.shutdown(); // 发送关闭线程池的指令 cancelTask(future, 2_000); // 在 2 秒以后取消该任务 try { boolean result = future.get(); System.out.format("%d 是否为素数? %b\n", num, result); } catch (CancellationException ex) { System.err.println("任务被取消"); } catch (InterruptedException ex) { System.err.println("当前线程被中断"); } catch (ExecutionException ex) { System.err.println("任务执行出错"); } }
程序运行到 2 秒时候的输出:
程序的最终输出:
能够发现,虽然咱们取消了任务,Future
的 get
方法也对咱们的取消作出了响应(即抛出 CancellationException
异常),可是任务并无中止,而是直到任务运行完毕了,程序才结束。
查看 Future
的实现类 FutureTask
的源码,咱们来看一下调用 cancel(true)
究竟发生了什么:
原来 cancel(true)
方法的原理是向正在运行任务的线程发送中断指令 —— 即调用运行任务的 Thread
的 interrupt()
方法。
因此 若是一个任务是可取消的,那么它应该能够对 Thread
的 interrupt()
方法作出被取消时的响应。
而 Thread
的 isInterrupted()
方法,即可以用来判断当前 Thread
是否被中断。任务开始运行时,运行任务的线程确定没有被中断,因此 isInterruped()
方法会返回 false
;而 interrupt()
方法调用以后,isInterruped()
方法会返回 true
。
(由此咱们也能够知道,Thread.sleep
方法是能够对中断作出响应的)
因此咱们修改 PrimerTask
的 call
方法,让其能够对运行任务的线程被中断时作出中止运行(跳出循环)的响应:
@Override public Boolean call() throws Exception { // i < num 让任务有足够的运行时间 for (long i = 2; i < num; i++) { if (Thread.currentThread().isInterrupted()) { // 任务被取消 System.out.println("PrimerTask.call: 你取消我干啥?"); return false; } if (num % i == 0) { return false; } } return true; }
运行结果:
能够看到程序在 2 秒的时候中止了运行,任务被成功取消。
总结:若是要经过 Future
的 cancel
方法取消正在运行的任务,那么该任务一定是能够 对线程中断作出响应 的任务。经过 Thread.currentThread().isInterrupted()
方法,咱们能够判断任务是否被取消,从而作出相应的取消任务的响应。