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!
用户疑问
进程是否应等待循环执行完成?若不能,如何让应用关闭前等待循环执行完毕?
问题分析与解决方案
核心问题
server.shutdown=graceful作用范围有限:该配置仅负责Tomcat容器的优雅停机,等待HTTP请求处理完成,不会主动等待Spring异步线程池中的任务结束。- 线程池配置缺失关键参数:仅设置
setWaitForTasksToCompleteOnShutdown(true)不够,缺少setAwaitTerminationSeconds(xxx)指定任务完成的超时等待时间,默认情况下线程池不会等待直接终止。 - 变量可见性问题:
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_
相关产品推荐
相关产品推荐

