如何避免Cron任务重复执行?当前任务未完成时不触发新任务
先明确你的两个核心需求:
- 串行执行:当前任务未完成时,绝对不能触发新的任务实例
- 时间对齐:首次执行在12:00:00,后续任务需从12:00:10开始(也就是按10秒固定间隔的时间点调度)
先说说你现有方案的问题
你代码里用future.isDone()来检查是否有任务在运行,这个逻辑完全无效。因为你用CronTrigger调度的是周期性任务,ScheduledFuture.isDone()只有在整个调度任务被取消、终止时才会返回true,正常运行期间它一直是false——也就是说这个判断根本没起到任何防重复的作用。
那为什么你的日志里看起来任务是串行的?其实是依赖了ThreadPoolTaskScheduler的默认特性:它底层的ScheduledThreadPoolExecutor默认核心线程数是1,所以即使Cron到点触发,新任务也会排队等待,只能串行执行。但这个依赖非常不可靠——如果后续你调整线程池大小(比如设置taskScheduler.setPoolSize(3)),立刻就会出现多个任务实例并行执行的情况,完全违反你的第一个需求。
你的日志验证
从你给出的日志来看,任务确实是串行的,但这是线程池排队的结果,不是你的代码逻辑控制的。比如日志里出现了scheduler task1、scheduler task2、scheduler task3不同线程名,说明线程池已经创建了多个线程,但因为核心线程数只有1,新任务只能排队,所以没有并行。但这不是稳定的防重复机制。
满足需求的正确实现方式
要同时满足你的两个需求,需要做两部分调整:
1. 添加可靠的互斥锁,强制串行
在你的RunnableTask里加入ReentrantLock,用tryLock()尝试获取锁,获取不到就直接跳过当前触发的任务——这样无论线程池有多少线程,同一时间只有一个任务能执行核心逻辑:
private class RunnableTask implements Runnable { private Task task; private int times; private final ReentrantLock executionLock = new ReentrantLock(); // 互斥锁 private RunnableTask(Task task) { this.task = task; } private void println(String string) { System.out.println(Thread.currentThread().getName() + string + " timestamp:" + LocalTime.now().format(DateTimeFormatter.ofPattern("mm:ss"))); } @Override public void run() { // 尝试获取锁,获取不到则跳过当前任务 if (!executionLock.tryLock()) { println(" skip: " + task.getId() + " times: " + times + " (previous task still running)"); return; } try { println(" begin: " + task.getId() + " times: " + times); // 替换为你的文件读写操作 FileInputStream fis = new FileInputStream("D:\\test\\test.exe"); FileOutputStream fos = new FileOutputStream("D:\\test\\a-" + times + ""); fos.write(IOUtils.toByteArray(fis)); fos.flush(); fos.close(); fis.close(); println(" end: " + task.getId() + " times: " + times); times++; } catch (Exception e) { e.printStackTrace(); } finally { executionLock.unlock(); // 必须释放锁,避免死锁 } } }
2. 调整Cron表达式对齐10秒间隔
你原来的Cron是0/3 * * * * *(每3秒执行一次),这和你“后续从12:00:10开始”的需求不符。要实现固定10秒间隔的时间点调度,应该用:
Task t1 = new Task(1L, "0/10 * * * * ?", 10000L);
这个Cron表达式表示每10秒执行一次,从0秒开始(也就是12:00:00、12:00:10、12:00:20...这些时间点触发)。
额外选项:如果允许下一次执行依赖上一次结束时间
如果你的需求不是严格对齐固定时间点,而是“上一次任务结束后10秒再执行下一次”,那可以不用CronTrigger,改用scheduleWithFixedDelay,这种方式天然保证串行,不需要额外加锁:
// 首次延迟0秒执行,之后每次任务结束后延迟10秒再执行 ScheduledFuture sf1 = taskScheduler.scheduleWithFixedDelay(new RunnableTask(t1), 0, 10, TimeUnit.SECONDS);
总结
你的现有方案不满足需求,当前的串行是线程池默认行为带来的巧合,不是可靠的防重复机制。必须添加互斥锁逻辑,同时调整Cron表达式(或改用固定延迟调度),才能真正满足你的两个核心需求。
内容的提问来源于stack exchange,提问作者xmcx

