解决crontab定时任务只能按照固定时间开始执行,不能实现立即执行的问题

import org.quartz.TriggerUtils;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.CronTask;

import java.text.ParseException;
import java.time.Duration;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

/**
 *  所用依赖:
 *     <dependencies>
 *         <dependency>
 *             <groupId>org.springframework.boot</groupId>
 *             <artifactId>spring-boot-starter-web</artifactId>
 *             <version>2.1.8.RELEASE</version>
 *         </dependency>
 *         <dependency>
 *             <groupId>org.quartz-scheduler</groupId>
 *             <artifactId>quartz</artifactId>
 *             <version>2.3.2</version>
 *         </dependency>
 *     </dependencies>
 */

/**
 * 解决crontab定时任务只能按照固定时间开始执行,不能实现立即执行的问题。
 * 问题描述:
 **  1. crontab表达式比如 0 0/10 * * * ?
 *   2. 当前时间是2021-07-20 17:58:00, 那么它接下来的执行时间
 *      只能是
 *          2021-07-20 18:00:00
 *          2021-07-20 18:10:00
 *          2021-07-20 18:20:00
 *          2021-07-20 18:30:00
 *      而无法是
 *          2021-07-20 17:58:00(第一次为立即执行)
 *          2021-07-20 18:08:00
 *          2021-07-20 18:18:00
 *          2021-07-20 18:28:00
 *   3. 这篇提供的代码能做到 执行时间为
 *          2021-07-20 17:58:00(第一次为立即执行)
 *          2021-07-20 18:10:00(第二次会根据周期间隔做修正,注意这里跳过了2021-07-20 18:00:00这个时刻)
 *          2021-07-20 18:20:00
 *          2021-07-20 18:30:00
 *      "根据周期间隔做修正"的意思就是说,假如当前时间是2021-07-20 17:52:00,那么执行时间就是
 *          2021-07-20 17:52:00(第一次为立即执行)
 *          2021-07-20 18:00:00(这一次就没有跳过18:00这个时刻)
 *          2021-07-20 18:10:00
 *          2021-07-20 18:20:00
 *          2021-07-20 18:30:00
 *
 */
@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);

        System.out.println(new Date());

        Task task = new Task();
        task.start();

    }
}

class Task{
    public void start() {
        String cron = "0 */1 * * * ?";
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(1);
        taskScheduler.initialize();
        CronTask discoverTask = new CronTask(new Executor(), cron);
        Trigger tmp = discoverTask.getTrigger();

        CronTriggerImpl cronTriggerImpl = new CronTriggerImpl();
        try {
            cronTriggerImpl.setCronExpression(cron);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        List<Date> dates = TriggerUtils.computeFireTimes(cronTriggerImpl, null, 3);
        for (int i = 0; i < dates.size(); i++) {
            System.out.println("接下来第" + (i + 1) + "次执行时间" + dates.get(i));
        }
        Duration between = Duration.between(dates.get(0).toInstant(), dates.get(1).toInstant()).abs();
        long step = between.getSeconds();
        if (step == 0) {
            step = 1;
        }
        System.out.println("执行周期为" + step + "秒");

        long finalStep = step;
        Trigger trigger = new Trigger() {
            AtomicLong count = new AtomicLong(1);
            double factor = 0.75; // 调节因子
            Date firstRunDate;

            @Override
            public Date nextExecutionTime(TriggerContext triggerContext) {
                if (count.get() == 1) { // 第一次用当前时间作为触发时间
                    firstRunDate = new Date();
                    count.incrementAndGet();
                    return firstRunDate;
                }
                if (count.get() == 2) {  // 第二次判断是否如期执行。如果该次时间和第一次执行时间"很近", 就忽略该次。
                    Date current = tmp.nextExecutionTime(triggerContext);
                    long past = Duration.between(firstRunDate.toInstant(), current.toInstant()).abs().getSeconds();
                    count.incrementAndGet();
                    if (past > (factor * finalStep)) {
                        System.out.println("第一次执行时间距下一次执行时间间隔" + past + "秒, 接近一个周期,run!");
                        return current;
                    }
                    System.out.println("第一次执行时间距下一次执行时间间隔" + past + "秒, 间隔太短,不run!");
                    return null;
                }
                return tmp.nextExecutionTime(triggerContext);
            }
        };
        taskScheduler.schedule(discoverTask.getRunnable(), trigger);
    }

    public class Executor implements Runnable {
        @Override
        public void run() {
            System.out.println("运行中,当前时间:" + new Date());
        }
    }
}

 

posted @ 2021-07-20 18:08  须小弥  阅读(993)  评论(0编辑  收藏  举报