ExecutorService 的 shutdown 和 shu
引子
当没有任务需要执行时,ExecutorService
不会自动被系统销毁,而是会继续存活并等待新的任务到来。如果你的 app 需要随时响应处理新提交的任务,那 ExecutorService
的这种生命周期的设计就很合适。但是一个 app 总有结束的时刻,当 app 结束时,ExecutorService
却并不会终止,它将导致 JVM 继续存活并运行。shutdown
和 shutdownNow
就是为关闭 ExecutorService
而设计的 API。
shutdown
shutdown
不会马上销毁 ExecutorService
线程资源,但它会保证其他线程不会再向 ExecutorService
提交新的任务。
executorService.shutdown();
shutdownNow
shutdownNow
会尝试马上销毁 ExecutorService
线程资源,但它不保证 ExecutorService
的线程池中的所有正在运行的线程在同一时刻被终止。shutdownNow
将会返回已在队列中等待被执行的任务。
List<Runnable> notExecutedTasks = executorService.shutDownNow();
区别和联系
-
shutdown
会使ExecutorService
不再接受新的任务,但是已经submit
的任务会继续执行 -
shutdownNow
会做同样的事,并且会通过中断( interrupt )相关线程来尝试取消已提交的任务,如果提交的任务忽略这个中断( interruption ),那么shutdownNow
方法的表现将和shutdown
一致。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class Thread_Shutdown {
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(1);
executor.submit(new Runnable() {
@Override
public void run() {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("interrupted");
break;
}
}
}
});
executor.shutdown();
System.out.println("shutdown");
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
System.out.println("Still waiting after 10s: calling System.exit(0)...");
System.exit(0);
}
System.out.println("Exiting normally...");
}
}
case 1
执行上面这段代码会有如下打印:
shutdown
Still waiting after 10s: calling System.exit(0)...
这是因为 shutdown
方法并不产生设置线程的中断标志,因此在任务的循环中 Thread.currentThread().isInterruped()
始终返回 false
。
case 2
将 shutdown
改为 shutdonwNow
,打印如下:
interrupted
shutdown
Exiting normally...
这是因为 shutdownNow
会设置任务线程的中断标志,因此代码中任务通过检测到 Thread.currentThread().isInterruped()
会立即退出。
case 3
保持 shutdown
,将 while
循环中的代码注释掉,让其一直空循环,打印如下:
shutdown
Still waiting after 10s: calling System.exit(0)...
这是因为任务中不再检查线程的中断标志,因此表现跟 shutdown
一致,并且任务是死循环,因此将一直执行下去,直到程序执行 System.exit(0)
。
最佳实践
终止 ExecutorService
的一个最佳实践就是,shutdown
和 shutdownNow
两个方法一起,并结合 awaitTermination
来实现超时等待。
executorService.shutdown();
try {
if (!executorService.awaitTermination(800, TimeUnit.MILLISECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException e) {
executorService.shutdownNow();
}
- 调用
shutdown
,阻止新提交任务,并让等待队列中的任务执行完成 - 调用
awaitTermination()
,保证等待队列中的任务最多执行 800 ms,以防止执行任务时间太长或被阻塞,而导致ExecutorService
不能被销毁。 - 在
awaitTermination
等待 800 ms 后,ExecutorService
中还有任务没执行完,则调用shutdownNow
强行终止,以释放ExecutorService
资源。 - 结合 Java 中 InterruptedException 的最佳实践 中,上面代码执行
awaitTermination
时所在的线程也有可能被 interrupt,因此需要 catch InterruptedException。