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