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

如何从@Scheduled方法优雅停止Spring Boot应用

数据库触发Spring Boot优雅关闭失败问题解决

问题场景

通过数据库值触发Spring Boot应用停止,实现代码如下:

@Scheduled(fixedDelay = 3000)
public void checkShutdown() {

    boolean shouldShutdown = readFromDatabase();

    if (shouldShutdown) {
        log.info("Shutdown requested.");
        SpringApplication.exit(applicationContext, () -> 0);
        return;
    }
}

异常信息

应用退出时抛出Hikari数据源关闭中断的异常:

2023-05-13T10:24:47.924-03:00  INFO 248948 --- [   scheduling-1] c.e.d.example.service.ShutdownService   : Shutdown requested.
2023-05-13T10:24:47.934-03:00  WARN 248948 --- [   scheduling-1] o.s.s.c.ThreadPoolTaskScheduler          : Interrupted while waiting for executor 'taskScheduler' to terminate
2023-05-13T10:24:47.939-03:00  INFO 248948 --- [   scheduling-1] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2023-05-13T10:24:47.944-03:00  INFO 248948 --- [   scheduling-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2023-05-13T10:24:47.949-03:00  WARN 248948 --- [   scheduling-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Interrupted during closing

java.lang.InterruptedException: null
    at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:1660) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor.awaitTermination(ThreadPoolExecutor.java:1464) ~[na:na]
    at com.zaxxer.hikari.pool.HikariPool.shutdown(HikariPool.java:243) ~[HikariCP-5.0.1.jar:na]
    at com.zaxxer.hikari.HikariDataSource.close(HikariDataSource.java:351) ~[HikariCP-5.0.1.jar:na]
    at org.springframework.beans.factory.support.DisposableBeanAdapter.destroy(DisposableBeanAdapter.java:222) ~[spring-beans-6.0.8.jar:6.0.8]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroyBean(DefaultSingletonBeanRegistry.java:587) ~[spring-beans-6.0.8.jar:6.0.8]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingleton(DefaultSingletonBeanRegistry.java:559) ~[spring-beans-6.0.8.jar:6.0.8]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingleton(DefaultListableBeanFactory.java:1189) ~[spring-beans-6.0.8.jar:6.0.8]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingletons(DefaultSingletonBeanRegistry.java:520) ~[spring-beans-6.0.8.jar:6.0.8]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingletons(DefaultListableBeanFactory.java:1182) ~[spring-beans-6.0.8.jar:6.0.8]
    at org.springframework.context.support.AbstractApplicationContext.destroyBeans(AbstractApplicationContext.java:1084) ~[spring-context-6.0.8.jar:6.0.8]
    at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:1053) ~[spring-context-6.0.8.jar:6.0.8]
    at org.springframework.context.support.AbstractApplicationContext.close(AbstractApplicationContext.java:1003) ~[spring-context-6.0.8.jar:6.0.8]
    at org.springframework.boot.SpringApplication.close(SpringApplication.java:1397) ~[spring-boot-3.0.6.jar:3.0.6]
    at org.springframework.boot.SpringApplication.exit(SpringApplication.java:1350) ~[spring-boot-3.0.6.jar:3.0.6]
    at com.example.service.ShutdownService.checkShutdown(ShutdownService.java:34) ~[classes/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
    at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84) ~[spring-context-6.0.8.jar:6.0.8]
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) ~[spring-context-6.0.8.jar:6.0.8]
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539) ~[na:na]
    at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305) ~[na:na]
    at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) ~[na:na]
    at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]

已尝试的无效方案

  • 配置Spring任务调度与执行的关闭等待参数:
spring:
  task:
    execution:
      shutdown:
        await-termination: true
        await-termination-period: 15s
    scheduling:
      shutdown:
        await-termination: true
        await-termination-period: 15s
  • 使用applicationContext.close()替代SpringApplication.exit()

解决方案

核心原因

关闭操作在调度线程(scheduling-1)中执行,Spring关闭流程会尝试终止调度线程池,导致当前执行关闭逻辑的线程被中断,进而打断Hikari连接池的关闭流程,抛出InterruptedException。

具体解决步骤

  1. 将关闭逻辑转移到新线程执行
    修改调度方法,把应用关闭的逻辑放到独立线程中,避免当前调度线程被中断:
@Scheduled(fixedDelay = 3000)
public void checkShutdown() {

    boolean shouldShutdown = readFromDatabase();

    if (shouldShutdown) {
        log.info("Shutdown requested.");
        // 启动新线程执行关闭逻辑
        new Thread(() -> SpringApplication.exit(applicationContext, () -> 0)).start();
        return;
    }
}
  1. 调整Hikari连接池关闭超时(可选)
    增加Hikari池的关闭等待时间,确保连接池有足够时间完成关闭:
spring:
  datasource:
    hikari:
      shutdown-timeout: 30000 # 30秒
  1. 提前取消定时任务(可选)
    在执行关闭前,取消当前的定时任务,避免后续调度任务干扰关闭流程:
@Autowired
private ThreadPoolTaskScheduler taskScheduler;

private ScheduledFuture<?> shutdownTask;

@PostConstruct
public void init() {
    shutdownTask = taskScheduler.scheduleWithFixedDelay(this::checkShutdown, 3000);
}

@Scheduled(fixedDelay = 3000)
public void checkShutdown() {

    boolean shouldShutdown = readFromDatabase();

    if (shouldShutdown) {
        log.info("Shutdown requested.");
        // 取消当前定时任务
        shutdownTask.cancel(true);
        // 启动新线程执行关闭逻辑
        new Thread(() -> SpringApplication.exit(applicationContext, () -> 0)).start();
        return;
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 01:42:04