- 工作这几年见到的所有工程的线程池基本上都是只管使用,不管关闭,虽然大部分都没什么问题,因为大多数关闭脚本中会有服务下线和等待时间,但是这样仅仅是绕开了问题,并不代表问题不存在
- 首先线程池如果想要确保正确关闭,要从三个方向考虑。第一个是线程是不是守护线程、第二个是线程池是否还可以接收任务、第三个是怎么知道线程池把任务都执行完了
- 上述第一点对应的是线程池中的ThreadFactory,其中会设置线程池中的线程是否是守护线程。第二点对应的是线程池的shutDown方法,执行过后调用sumbit或execute会被拒绝。第三点对应的是线程池的awaitTermination方法,如果线程池中的任务都执行完了则返回true否则false
- 所以可以如下做,如果线程池中的任务还没执行完jvm就不关闭
// 真正的工程不该使用Executors中快捷方法创建的线程池,这里只是演示作用
private static ExecutorService executorService = Executors.newCachedThreadPool();
static {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
executorService.shutdown();
try {
while (!executorService.awaitTermination(1, TimeUnit.SECONDS)) {
System.out.println("还没执行完");
}
} catch (InterruptedException e) {
// 忽略,执行这段逻辑的是jvm关闭的钩子线程,不用考虑中断问题
}
}));
}
- 因为现实场景中,一个任务的执行时间一定是有一个上线的,所以我们可以使用guava提供的快捷方法处理线程池的关闭问题,默认最长等待120秒,可以设置
//getExitingExecutorService会把线程池中的线程都改为守护线程,并且在jvm关闭时的钩子线程中执行shutDown和awaitTermination
//awaitTermination方法返回只有,不论线程池中的任务执没执行完,因为钩子线程已经执行完了,此时jvm中应该只剩下线程池的守护线程,所以这些守护线程也就随着jvm的关闭结束了
MoreExecutors.getExitingExecutorService(new ThreadPoolExecutor(5, 5, 10, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10)))
executor.setThreadFactory(new ThreadFactoryBuilder()
.setDaemon(true)
.setThreadFactory(executor.getThreadFactory())
.build());
addShutdownHook(MoreExecutors.newThread("DelayedShutdownHook-for-" + service, new Runnable() {
@Override
public void run() {
try {
// We'd like to log progress and failures that may arise in the
// following code, but unfortunately the behavior of logging
// is undefined in shutdown hooks.
// This is because the logging code installs a shutdown hook of its
// own. See Cleaner class inside {@link LogManager}.
service.shutdown();
service.awaitTermination(terminationTimeout, timeUnit);
} catch (InterruptedException ignored) {
// We're shutting down anyway, so just ignore.
}
}
}));
网友评论