如何正确停止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
相关产品推荐
相关产品推荐

