如何正确的关闭线程池
线程池是 Java 开发中常用的组件之一,在应用程序关闭的时候,需要将线程池关闭。但关闭线程池时,要按照不同的情况来使用不同的方式。下面介绍几种关闭线程池的常用方法。
本文基于 OpenJDK11
1. 线程池简介
Java 中的线程池都实现了 Executor
接口,这个接口提供了执行任务的入口。ExecutorService 继承了 Executor,提供了对线程池生命周期的管理,在开发中用的最多。
对于关闭线程池,ExecutorService 提供了三种方式:
- shutdown
- shutdownNow
- awaitTermination
这几种方法都可以关闭线程池,但是每种方式之间有一些差别。
现有如下任务:
class Task implements Runnable {
@Override
public void run() {
System.out.println("任务开始执行");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务执行结束");
}
}
调用 shutdown
方法之后,线程池就不会再接收新的任务,如果强行添加,就会报错。而线程池中的任务会继续执行,但是却不保证这些任务能够执行完成,具体原因,下文会说到。
ExecutorService threadPool = Executors.newFixedThreadPool(2);
threadPool.submit(new Task());
threadPool.shutdown();
threadPool.submit(new Task());
上面代码的执行结果如下:
java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@7e07db1f
调用 shutdownNow
方法之后,所以运行中的任务都会终止,而且会返回队列中等待的任务。
ExecutorService threadPool = Executors.newFixedThreadPool(1);
threadPool.submit(new Task());
threadPool.shutdownNow();
调用 awaitTermination
之后,线程池中的任务会继续运行,而且还可以接受新的任务。它是线程阻塞的,会等到任务执行完成才结束主线程,这是它与shutdown 的区别。当然也不会无限期的等下去,可以通过设置超时时间,在超时时间之后,就会结束线程的阻塞。
ExecutorService threadPool = Executors.newFixedThreadPool(1);
threadPool.submit(new Task());
threadPool.awaitTermination(6, TimeUnit.SECONDS);
2. 正确的关闭线程池
执行简单任务
还以上面的 Task 为例,这种 Task 不依赖外部的数据,执行这种任务的线程池在关闭时,只需要调用 shutdown 方法即可。
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(2);
threadPool.submit(new Task());
threadPool.submit(new Task());
threadPool.shutdown();
System.out.println("主线程结束");
}
这时程序的输出如下,任务会在主线程结束之后继续执行。
任务开始执行
任务开始执行
主线程结束
任务执行结束
任务执行结束
执行复杂任务
在执行一些复杂任务时,任务需要依赖外部的数据,任务为下:
class Task1 implements Runnable {
public Map<String, String> data;
public Task1(Map<String, String> o) {
this.data = o;
}
@Override
public void run() {
while (data.containsKey("invoke")) {
System.out.println("正常任务执行!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("正常任务结束");
}
}
在线程池中执行这个任务:
public static void main(String[] args) throws InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(2);
Map<String, String> data = new HashMap<>();
data.put("invoke", "true");
threadPool.submit(new Task1(data));
Thread.sleep(1000);
threadPool.shutdown();
data.remove("invoke");
System.out.println("主线程结束");
}
程序执行结果如下,在外部数据依赖删除之后,程序也就执行结束了。
正常任务执行!
主线程结束
正常任务结束
如果这个依赖是一些网络连接,或者数据库连接,在主程序退出之后,这些连接就会被销毁,那么线程池中的任务就无法继续执行。所以这个时候需要阻塞主线程,给线程池中的任务一些执行的时间。
public static void main(String[] args) throws InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(2);
Map<String, String> data = new HashMap<>();
data.put("invoke", "true");
threadPool.submit(new Task1(data));
Thread.sleep(1000);
threadPool.shutdown();
try {
if (!threadPool.awaitTermination(4, TimeUnit.SECONDS)) {
System.out.println("任务还没结束");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
data.remove("invoke");
System.out.println("主线程结束");
}
调用了 awaitTermination 方法之后,主线程就会被阻塞,等待线程池中的任务继续执行,但是也不会无限期的等下去,等待超时时间过了之后,主程序还是会退出。
在关闭线程池时,一般是 shutdown 与 awaitTermination 方法配合使用。
threadPool.shutdown();
if (!threadPool.awaitTermination(5, TimeUnit.SECONDS)) {
System.out.println("任务还没结束");
}
如果还想确认在线程池退出时,确保线程池中的任务全部结束,可以在阻塞时间过了之后使用 shutdownNow:
threadPool.shutdown();
if (!threadPool.awaitTermination(5, TimeUnit.SECONDS)) {
System.out.println("任务还没结束");
threadPool.shutdownNow();
}
这样就可以让线程池按照预想的结果关闭。
文 / Rayjun