ExecutorService中shutdown、shutdownNow等的区别及使用

引子

当没有任务需要执行时,ExecutorService 不会自动被系统销毁,而是会继续存活并等待新的任务到来。如果你的 app 需要随时响应处理新提交的任务,那 ExecutorService 的这种生命周期的设计就很合适。但是一个 app 总有结束的时刻,当 app 结束时,ExecutorService 却并不会终止,它将导致 JVM 继续存活并运行。shutdown 和 shutdownNow 就是为关闭 ExecutorService 而设计的 API。

一、方法说明

1、shutdown():停止接收新任务,原来的任务继续执行

英文原意:关闭,倒闭;停工。 这里的意思是 关闭线程池 与使用数据库连接池一样,每次使用完毕后,都要关闭线程池。

1. 停止接收新的submit的任务;2. 已经提交的任务(包括正在跑的和队列中等待的),会继续执行完成;3. 等到第2步完成后,才真正停止;

2、shutdownNow():停止接收新任务,原来的任务停止执行

  1. 跟 shutdown() 一样,先停止接收新submit的任务;
  2. 忽略队列里等待的任务;
  3. 尝试将正在执行的任务interrupt中断;
  4. 返回未执行的任务列表;

说明:它试图终止线程的方法是通过调用 Thread.interrupt() 方法来实现的,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt() 方法是无法中断当前的线程的。所以,shutdownNow() 并不代表线程池就一定立即就能退出,它也可能必须要等待所有正在执行的任务都执行完成了才能退出。但是大多数时候是能立即退出的。

3、awaitTermination(long timeOut, TimeUnit unit):当前线程阻塞

timeout 和 TimeUnit 两个参数,用于设定超时的时间及单位

当前线程阻塞,直到:

  • 等所有已提交的任务(包括正在跑的和队列中等待的)执行完;
  • 或者 等超时时间到了(timeout 和 TimeUnit设定的时间);
  • 或者 线程被中断,抛出InterruptedException

然后会监测 ExecutorService 是否已经关闭,返回true(shutdown请求后所有任务执行完毕)或 false(已超时)

二、区别

1、shutdown() 和 shutdownNow() 的区别

shutdown() 只是关闭了提交通道,用submit()是无效的;而内部该怎么跑还是怎么跑,跑完再停。 shutdownNow() 能立即停止线程池,正在跑的和正在等待的任务都停下了。

2、shutdown() 和 awaitTermination() 的区别

shutdown() 后,不能再提交新的任务进去;但是 awaitTermination() 后,可以继续提交。 awaitTermination() 是阻塞的,返回结果是线程池是否已停止(true/false); shutdown() 不阻塞。

三、最佳实践

终止 ExecutorService 的一个最佳实践就是,shutdown 和 shutdownNow 两个方法一起,并结合 awaitTermination 来实现超时等待。

executorService.shutdown();
try {
    if (!executorService.awaitTermination(800, TimeUnit.MILLISECONDS)) {
        executorService.shutdownNow();
    } 
} catch (InterruptedException e) {
    executorService.shutdownNow();
}
  1. 调用 shutdown ,阻止新提交任务,并让等待队列中的任务执行完成
  2. 调用 awaitTermination() ,保证等待队列中的任务最多执行 800 ms,以防止执行任务时间太长或被阻 塞,而导致 ExecutorService 不能被销毁。
  3. awaitTermination 等待 800 ms 后, ExecutorService 中还有任务没执行完,则调用 shutdownNow 强行终止,以释放 ExecutorService 资源。
  4. 上面代码执行 awaitTermination 时所在的线程也有可能被 interrupt ,因此需要 catch InterruptedException