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

调整服务器时间至当前时间前,如何让@Scheduled(cron=xxx)正常运行?

问题描述

我有23个标注了@Scheduled注解的方法,全部用cron表达式配置定时规则。某天把服务器时间调晚数小时后改回原时间,发现所有@Scheduled方法都停止工作了。

一开始以为是线程阻塞,给所有方法加了@Async注解,但问题没改善,调整时间再回退后,定时任务还是没法运行。通过VisualVM导出线程快照,发现@Async用的timing-pool线程池里的所有线程都处于WAITING状态:

"timingPool-12" #66 prio=5 os_prio=0 tid=0x00000000216de000 nid=0x4fc4 waiting on condition [0x00000000333ce000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000006c3791b28> (a java.util.concurrent.SynchronousQueue$TransferStack)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.SynchronousQueue$TransferStack.awaitFulfill(SynchronousQueue.java:458)
        at java.util.concurrent.SynchronousQueue$TransferStack.transfer(SynchronousQueue.java:362)
        at java.util.concurrent.SynchronousQueue.take(SynchronousQueue.java:924)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - None
...

推测问题根源是@Scheduled(cron)在服务器时间被调至当前时间之前时出现的逻辑错误。把所有@Scheduled(cron = xxx)改成@Scheduled(fixedDelay = xxx)后,线程没再出现阻塞情况。

现在测试环境需要调整服务器时间验证业务逻辑,想知道怎么确保@Scheduled(cron = xxx)在调整服务器时间(尤其是调至当前时间前)时仍能正常运行?

解决方案

1. 替换调度器为基于单调时钟的实现

Spring默认的ThreadPoolTaskScheduler依赖系统时间,当时间回退时会导致下一次执行时间计算异常。可以自定义调度器,使用单调递增时钟(基于System.nanoTime())来计算任务间隔,避免系统时间回退的影响:

@Configuration
public class SchedulerConfig {
    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler() {
            @Override
            protected ScheduledExecutorService createExecutor(int poolSize, ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
                return new ScheduledThreadPoolExecutor(poolSize, threadFactory, rejectedExecutionHandler) {
                    @Override
                    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
                        long startNanos = System.nanoTime() + unit.toNanos(initialDelay);
                        return super.scheduleAtFixedRate(() -> {
                            long nowNanos = System.nanoTime();
                            if (nowNanos >= startNanos) {
                                command.run();
                                startNanos += unit.toNanos(period);
                            }
                        }, initialDelay, period, unit);
                    }
                };
            }
        };
        scheduler.setPoolSize(10);
        scheduler.setThreadNamePrefix("custom-scheduler-");
        scheduler.initialize();
        return scheduler;
    }
}

2. 改用Quartz作为调度框架

Quartz本身对系统时间变化有更好的兼容性,它会定期检查系统时间,当检测到时间回退时会重新计算任务触发时间。只需引入Quartz依赖,配置SchedulerFactoryBean,即可用@Scheduled或Quartz原生API定义任务,无需修改原有cron表达式逻辑。

3. 监听系统时间变化,重启定时任务

在测试环境中,可以添加后台线程监听系统时间,当检测到时间回退超过阈值时,重新注册所有定时任务:

@Component
public class TimeChangeListener {
    @Autowired
    private ScheduledTaskRegistrar taskRegistrar;
    private long lastRecordedTime = System.currentTimeMillis();

    @PostConstruct
    public void startTimeMonitor() {
        new Thread(() -> {
            while (true) {
                long currentTime = System.currentTimeMillis();
                // 检测到时间回退超过1分钟时触发重启
                if (currentTime < lastRecordedTime - 60000) {
                    taskRegistrar.destroy();
                    taskRegistrar.afterPropertiesSet();
                }
                lastRecordedTime = currentTime;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }).start();
    }
}

4. 测试环境用模拟时间工具替代修改系统时间

如果只是为了验证业务逻辑,没必要直接修改服务器时间。可以用PowerMock或自定义时间工具类,替换业务代码中的时间获取逻辑,模拟时间变化,完全避免影响定时任务的调度逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 02:01:00