调用shutdown后ScheduledExecutorService仍运行如何取消定时任务
问题根因
ScheduledExecutorService调用shutdown()后仍执行周期任务是符合API设计语义的,不是异常:
shutdown()的作用仅为阻止新任务提交,已进入等待队列的周期任务默认会继续执行已调度的轮次,不会被自动清除- 你提供的示例代码本身存在两处逻辑错误,进一步导致日志时序和执行行为不符合预期:
funcA("shutdown-")被写在了shutdown()方法的外部,类实例化时就会执行该日志打印,根本不是shutdown流程触发的输出,日志时序完全不可靠- 线程池终止等待逻辑存在语法问题,第二个
awaitTermination未传入合法的超时时间参数,且任务内部未做中断响应,就算调用shutdownNow()也无法快速终止正在运行的任务
彻底取消周期任务的正确实现
不要依赖线程池shutdown方法自动清理周期任务,按以下步骤实现可以完全终止调度:
- 保存周期调度方法返回的
ScheduledFuture实例,这是绑定对应周期任务的唯一精准控制句柄 - 执行shutdown流程时,首先调用
ScheduledFuture.cancel()主动取消周期任务,禁止后续轮次调度:如果需要中断正在执行的任务传true,允许当前正在跑的任务执行完再停传false - 任务内部增加中断响应逻辑,不要吞InterruptedException,收到中断信号后快速退出执行
- 补全线程池终止等待逻辑,设置合理的超时时间,最终保留线程中断状态
修正后的可运行代码
import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; class Test { private static final Logger LOGGER = LoggerFactory.getLogger(Test.class); // 配置合理的终止等待超时 private static final long INITIAL_TERMINATION_WAIT_SEC = 5; private static final long RETRY_TERMINATION_WAIT_SEC = 5; private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); // 保存周期任务的调度句柄 private ScheduledFuture<?> periodicTaskHandle; public Test() { periodicTaskHandle = scheduler.scheduleWithFixedDelay(() -> { funcA("periodicRun-"); }, 15, 15, TimeUnit.SECONDS); } private void funcA(String path) { LOGGER.info("Running the method " + path); } public void shutdown() { // 主动取消周期任务,禁止后续轮次调度,中断正在执行的任务 if (periodicTaskHandle != null) { periodicTaskHandle.cancel(true); } scheduler.shutdown(); try { // 等待现有任务执行终止 if (!scheduler.awaitTermination(INITIAL_TERMINATION_WAIT_SEC, TimeUnit.SECONDS)) { scheduler.shutdownNow(); // 等待任务响应中断后退出 scheduler.awaitTermination(RETRY_TERMINATION_WAIT_SEC, TimeUnit.SECONDS); } } catch (InterruptedException ie) { scheduler.shutdownNow(); Thread.currentThread().interrupt(); } // shutdown流程完成后再打印日志,修正原来的位置错误 funcA("shutdown-"); } }
注意:如果你的周期任务里存在阻塞操作(比如IO、锁等待、sleep),必须在捕获InterruptedException后退出任务逻辑,不要吞掉异常继续执行,否则就算发了中断信号任务也不会停止。
内容的提问来源于stack exchange,提问作者coderatcloud9
相关产品推荐
相关产品推荐

