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()); } } }