线程池未争取关闭导致的一个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方法,退出线程池。

posted @ 2025-09-20 17:34  Hekk丶  阅读(8)  评论(0)    收藏  举报