线程池未争取关闭导致的一个bug
前提
自己写了一个任务执行器TaskStarter,在SpringBoot启动时自动执行一些初始化任务。使用数据库来保证一些幂等操作。
为了防止任务执行过久或者执行过程中失去响应,每个任务执行时都需要启动一个异步的任务来更新任务的RUN_TIME。
这个异步任务的执行由一个scheduledExecutor执行。
当任务执行失败时,应该在控制台打印错误并退出spring boot的启动。但现实情况是程序并未退出。
代码
任务执行器实现了ApplicationRunner
接口,由SpringBoot在应用启动时调用run()方法执行业务逻辑。
并且构造函数中注册了jvm退出函数,在退出时关闭线程池。
@Component
@Slf4j
public class TaskStarter implements ApplicationRunner {
protected ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(1);
public TaskStarter() {
this.threadPoolExecutor = new ThreadPoolExecutor(5,
5,
30,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100),
new ThreadFactory() {
private final AtomicInteger integer = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "taskRunner-" + integer.getAndIncrement());
}
});
this.threadPoolExecutor.allowCoreThreadTimeOut(true);
SpringApplication.getShutdownHandlers().add(() -> {
//关闭线程池,忽略队列中的任务
threadPoolExecutor.shutdownNow();
scheduledExecutor.shutdownNow();
});
}
}
此处之2个细节:
1、通过Executors创建的线程池的线程都是非daemon的
2、导致jvm退出的几种情况:
正常关闭:当最后一个非守护线程结束或者调用了System.exit或者通过其他特定平台的方法关闭(发送SIGINT,SIGTERM信号等)
强制关闭:通过调用Runtime.halt方法或者是在操作系统中直接kill(发送SIGKILL信号)掉JVM进程
异常关闭:运行中遇到RuntimeException异常等。
在Spring Boot启动报错后,由于scheduledExecutor
中的线程是非daemon导致jvm未退出。
解决方法
TaskStarter实现SmartLifecycle接口,并在stop()方法中调用线程池的shutdown方法,退出线程池。