当通过shutdownNow来强行关闭ExecutorService时,它会尝试取消正在执行的任务并返回所有已提交但尚未开始的任务;注意:调用shutdownNow在关闭过程中只会返回尚未开始的任务,而不会返回正在执行的任务。然而,我们无法通过常规方法来找出哪些任务已经开始但尚未结束。这意味着我们无法在关闭过程中知道正在执行的任务的状态,除非任务本身会执行某种检查。
public class Test { public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(3); for (int i = 0; i < 100; i++) { final int finalI = i; service.execute(new Runnable() { @Override public void run() { //...... System.out.println(finalI); } }); } // 返回已提交但尚未开始的任务集合 List<Runnable> list = service.shutdownNow(); System.out.println(list.toString()); } }以上执行一次结果:
可见返回的任务集合,每个元素concurrent.Test$1@443b7951就是一个任务,其中concurrent为我的包名,Test为类名
由于不能返回正在执行而被中断的任务,所以这也可以说是shutdownNow的局限性,如果不自定义对这些被遗漏的任务进行处理,那么将是糟糕的的代码。日常应该使用shutdown方法来正常关闭;如果非要使用shutdownNow,可以在ExecutorService中跟踪关闭之后被取消的任务:
public class TrackingExecutor extends AbstractExecutorService{ private final ExecutorService service; private final Set<Runnable> tasksCancelledAtShutdown= Collections.synchronizedSet(new HashSet<Runnable>()); TrackingExecutor(ExecutorService service){ this.service=service; } public List<Runnable> getCancelledTasks(){ if(!service.isTerminated()){ throw new IllegalStateException(""); } return new ArrayList<>(tasksCancelledAtShutdown); } @Override public void execute(final Runnable command) { service.execute(new Runnable() { @Override public void run() { try { command.run(); }finally { if (isShutdown()&&Thread.currentThread().isInterrupted()){ System.out.println("被打断了"+command); tasksCancelledAtShutdown.add(command); } } } }); } @Override public void shutdown() { // 将ExecutorService的其他方法委托给service service.shutdown(); } @Override public List<Runnable> shutdownNow() { // 将ExecutorService的其他方法委托给service return service.shutdownNow(); } @Override public boolean isShutdown() { // 将ExecutorService的其他方法委托给service return service.isShutdown(); } @Override public boolean isTerminated() { // 将ExecutorService的其他方法委托给service return service.isTerminated(); } @Override public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { // 将ExecutorService的其他方法委托给service return service.awaitTermination(timeout,unit); } }以上程序中,在任务执行过程中,如果调用了shutdownNow方法,那么此时isShutdown为true,并且此时如果任务没有执行完毕,那么将会被打断,我们要将这个任务放到一个特殊的集合,以便后续处理。
我们将上面的Test案例用TrackingExecutor测试一下
public class Test { private volatile TrackingExecutor executor; public synchronized void start() { executor = new TrackingExecutor(Executors.newFixedThreadPool(3)); for (int i = 0; i < 5; i++) { final int finalI = i; executor.execute(new Runnable() { @Override public void run() { //...... System.out.println(finalI); } }); } } public synchronized void stop() throws InterruptedException { try { System.out.println("已提交尚未开始= " + (executor.shutdownNow())); if (executor.awaitTermination(1000, TimeUnit.MILLISECONDS)) { System.out.println("正在运行而被取消= " + executor.getCancelledTasks()); } } finally { executor = null; } } public static void main(String[] args) throws InterruptedException { Test test = new Test(); test.start(); test.stop(); } }运行一次的结果:
由于shutdownNow方法本来就会返回已提交尚未执行的任务集合,而上面收集的是正在运行而被打断的任务集合,这样两种状态的集合我们都拿到了,就可以做后续的处理了,常见的应用案例是网页爬虫,网页爬虫程序的工作通常是无穷尽的,因此当爬虫程序必须关闭时,我们通常希望保存它的状态,以便稍后重新启动(例如当爬虫程序关闭时,无论是还没有开始的任务,还是那些被取消的任务,都将记录他们的URL,因此当爬虫程序重新启动时,就可以将这些url的页面抓取任务加入到任务队列中)。
参考《Java并发编程实战》