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

如何正确停止TimerTask?Spring Boot下VPN延迟销毁任务优化

修复VPN延迟销毁任务的取消逻辑(替代废弃的thread.stop())

核心思路

  • 不再通过遍历线程的方式取消任务,直接持有任务句柄(ScheduledFuture)调用cancel()实现安全取消
  • 用Spring官方推荐的TaskScheduler替代原生Timer,适配Spring Boot环境,规避Timer单线程缺陷
  • 在任务执行前后增加中断状态检查,同时给销毁操作加幂等性校验,避免重复执行

具体实现代码

1. 注入Spring TaskScheduler并维护任务句柄

import org.springframework.scheduling.TaskScheduler;
import java.util.concurrent.ScheduledFuture;

@Service
public class VpnServiceImpl {
    private final TaskScheduler taskScheduler;
    // 用volatile保证多线程下的可见性
    private volatile ScheduledFuture<?> currentTearDownTask;

    // 构造函数注入依赖
    public VpnServiceImpl(TaskScheduler taskScheduler, /* 其他依赖如txManager、em等 */) {
        this.taskScheduler = taskScheduler;
        // 初始化其他依赖
    }

2. 修改REST请求处理逻辑

收到请求时先取消旧任务,再创建新的延迟任务:

// 处理REST请求的方法
public void scheduleVpnTearDown(Vpn vpn) {
    // 取消现有未完成的任务
    if (currentTearDownTask != null && !currentTearDownTask.isDone()) {
        boolean cancelled = currentTearDownTask.cancel(true);
        if (cancelled) {
            log.debug("已取消旧的VPN延迟销毁任务");
        }
    }

    // 调度新的延迟任务
    currentTearDownTask = taskScheduler.schedule(
        () -> executeTearDown(vpn),
        this.vpnProperties.delay()
    );
    log.debug("已创建新的VPN延迟销毁任务,延迟时长:{}ms", this.vpnProperties.delay().toMillis());
}

3. 实现安全的销毁任务执行逻辑

private void executeTearDown(Vpn vpn) {
    // 先检查线程是否被中断,直接终止任务
    if (Thread.currentThread().isInterrupted()) {
        log.debug("VPN销毁任务已被中断,终止执行");
        return;
    }

    TransactionStatus tx = this.txManager.getTransaction(new DefaultTransactionDefinition());
    try {
        var vpnToUpdate = this.em.find(Vpn.class, vpn.getId());

        // 事务启动后再次检查中断状态,避免无效操作
        if (Thread.currentThread().isInterrupted()) {
            this.txManager.rollback(tx);
            log.debug("VPN销毁任务执行中被中断,回滚事务");
            return;
        }

        doTearDown(vpnToUpdate);
        this.txManager.commit(tx);
        log.debug("VPN销毁任务执行完成");
    } catch (RuntimeException e) {
        log.error("Tear Down Error: {}", e.getMessage(), e);
        if (!tx.isCompleted()) {
            this.txManager.rollback(tx);
        }
    } finally {
        // 任务完成后清空句柄,避免持有已完成任务的引用
        if (currentTearDownTask != null && currentTearDownTask.isDone()) {
            currentTearDownTask = null;
        }
    }
}

// 原销毁方法增加幂等性校验
private VpnStatusS2SDto doTearDown(Vpn vpn) {
    log.debug("开始执行VPN Pod销毁操作");

    // 幂等性检查:如果VPN已处于销毁状态,直接返回
    if (VpnStatus.DESTROYED.equals(vpn.getStatus())) {
        log.debug("VPN已处于销毁状态,无需重复操作");
        return mapper.toVpnStatusDto(vpn);
    }

    this.client
        .pods()
        .inNamespace(this.kubeProps.getNamespace())
        .withLabel("app", "vpn-gateway")
        .withLabel("app.kubernetes.io/component", "vpn")
        .delete();

    vpn.setModifiedDate(Instant.now());
    vpn.setStatus(VpnStatus.DESTROYED); // 假设存在状态字段标记销毁状态
    this.em.persist(vpn);
    return mapper.toVpnStatusDto(vpn);
}

关键说明

  • ScheduledFuture.cancel(true):向任务线程发送中断信号,配合任务内部的Thread.currentThread().isInterrupted()检查,能安全终止正在执行的任务,替代危险的thread.stop()
  • 幂等性校验:即使任务因特殊原因未被及时取消,也不会重复执行销毁操作,避免资源浪费
  • Spring TaskScheduler:相比原生Timer支持多线程任务执行,与Spring容器集成更紧密,适配Spring Boot生态

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 12:01:21