写点什么

如何正确的关闭线程池

用户头像
Rayjun
关注
发布于: 2020 年 11 月 14 日
如何正确的关闭线程池

线程池是 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

本文首发于微信公众号



欢迎关注我的微信公众号



发布于: 2020 年 11 月 14 日阅读数: 34
用户头像

Rayjun

关注

程序员,王小波死忠粉 2017.10.17 加入

非著名程序员,还在学习如何写代码,公众号同名

评论

发布
暂无评论
如何正确的关闭线程池