You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot优雅停机未等待异步循环任务执行完成问题

问题描述

为应用实现优雅停机功能并测试,预期控制台最终输出"Done!",但实际未达预期。

初始代码实现

@Service
public class MyClass {
    private boolean keepRunning = true;

    @Async
    public void myMethod(){

        do {
            try {
                System.out.println("Start");
                Thread.sleep(10000);
                System.out.println("Done!");
            }catch (Exception e){
                logger.error(e.getLocalizedMessage());
            }
        } while(keepRunning);
    }


    @PreDestroy
    void stop() {
        System.out.println("Shutdown called!");
        this.keepRunning = false;
    }
}

配置

在application.properties中添加:

server.shutdown=graceful

初始终止时控制台输出

Start
2024-11-09T12:58:52.932-05:00  INFO 7374 --- [myapp] [ionShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown        : Commencing graceful shutdown. Waiting for active requests to complete
2024-11-09T12:58:52.935-05:00  INFO 7374 --- [myapp] [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown        : Graceful shutdown complete
Shutdown called!

Process finished with exit code 1

编辑补充:自定义线程池代码

实现了自定义taskExecutor:

@Bean(name = "test")
public ThreadPoolTaskExecutor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(1);
    executor.setMaxPoolSize(1);
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.setThreadNamePrefix("test-");
    executor.initialize();
    return executor;
}

修改方法指定使用该线程池:

@Async("test")
public void myMethod(){
    do {
        try {
            System.out.println("Start " + Thread.currentThread().getName());
            Thread.sleep(10000);
            System.out.println("Done!");
        }catch (Exception e){
            logger.error(e.getLocalizedMessage());
        }
    } while(keepRunning);
}

补充后终止时控制台输出

Start test-1
2024-11-09T14:23:18.132-05:00  INFO 14449 --- [myapp] [ionShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown        : Commencing graceful shutdown. Waiting for active requests to complete
2024-11-09T14:23:18.137-05:00  INFO 14449 --- [myapp] [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown        : Graceful shutdown complete
Shutdown called!

用户疑问

进程是否应等待循环执行完成?若不能,如何让应用关闭前等待循环执行完毕?


问题分析与解决方案

核心问题

  1. server.shutdown=graceful作用范围有限:该配置仅负责Tomcat容器的优雅停机,等待HTTP请求处理完成,不会主动等待Spring异步线程池中的任务结束。
  2. 线程池配置缺失关键参数:仅设置setWaitForTasksToCompleteOnShutdown(true)不够,缺少setAwaitTerminationSeconds(xxx)指定任务完成的超时等待时间,默认情况下线程池不会等待直接终止。
  3. 变量可见性问题:keepRunning为普通布尔变量,多线程环境下异步线程可能无法及时感知其值的变化,导致循环无法及时退出。

修复步骤

1. 完善线程池配置

添加等待终止的超时时间,确保线程池有足够时间等待当前任务执行完毕:

@Bean(name = "test")
public ThreadPoolTaskExecutor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(1);
    executor.setMaxPoolSize(1);
    executor.setWaitForTasksToCompleteOnShutdown(true);
    // 设置超时时间(需大于单次任务执行的10秒)
    executor.setAwaitTerminationSeconds(15);
    executor.setThreadNamePrefix("test-");
    executor.initialize();
    return executor;
}

2. 保证变量的线程安全性

用volatile修饰keepRunning,确保多线程环境下的可见性:

@Service
public class MyClass {
    // volatile保证异步线程能及时感知变量变化
    private volatile boolean keepRunning = true;

    @Async("test")
    public void myMethod(){
        do {
            try {
                System.out.println("Start " + Thread.currentThread().getName());
                Thread.sleep(10000);
                System.out.println("Done!");
            }catch (Exception e){
                logger.error(e.getLocalizedMessage());
            }
        } while(keepRunning);
    }

    @PreDestroy
    void stop() {
        System.out.println("Shutdown called!");
        this.keepRunning = false;
    }
}

3. 验证逻辑

应用关闭时的执行流程:

  • Tomcat先完成优雅停机,停止接收新请求
  • Spring销毁Bean时执行@PreDestroy,将keepRunning设为false
  • 线程池因配置了等待参数,会等待当前任务完成(执行完Thread.sleep(10000)并输出Done!)后再关闭
  • 最终应用正常退出,控制台会输出预期的Done!

额外说明

  • 若任务执行时间不确定,需根据业务场景设置合理的awaitTerminationSeconds值,避免无限等待导致应用无法退出
  • 如需更精细控制,可在@PreDestroy中主动控制线程池的关闭逻辑:
@Autowired
@Qualifier("test")
private ThreadPoolTaskExecutor taskExecutor;

@PreDestroy
void stop() {
    System.out.println("Shutdown called!");
    this.keepRunning = false;
    taskExecutor.shutdown();
    try {
        // 等待任务完成,最多15秒
        if (!taskExecutor.awaitTermination(15, TimeUnit.SECONDS)) {
            // 超时后强制终止(可选)
            taskExecutor.shutdownNow();
        }
    } catch (InterruptedException e) {
        taskExecutor.shutdownNow();
    }
}

内容的提问来源于stack exchange,提问作者Ar_

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 08:30:56