What is the expected behaviour of spring @scheduled cron when jobs would overlap?
The behaviour of fixedRate
and cron
is different.
Overlapping jobs are queued for fixedRate
(as per the above answer from @Igor).
Overlapping jobs are skipped for cron
.
Sample Java code to demonstrate the difference:
int i = 0;
@Scheduled(fixedRate = 5000)
public void test() throws InterruptedException {
Date start = new Date();
if (i < 3) Thread.sleep(10000);
i++;
System.out.printf("start %TT, finish %TT, i = %s%n", start, new Date(), i);
}
And the output:
start 13:25:30, finish 13:25:40, i = 1
start 13:25:40, finish 13:25:50, i = 2
start 13:25:50, finish 13:26:00, i = 3
start 13:26:00, finish 13:26:00, i = 4
start 13:26:00, finish 13:26:00, i = 5
start 13:26:00, finish 13:26:00, i = 6
start 13:26:00, finish 13:26:00, i = 7
start 13:26:05, finish 13:26:05, i = 8
start 13:26:10, finish 13:26:10, i = 9
start 13:26:15, finish 13:26:15, i = 10
As can be seen, the overlapping jobs are queued and start as soon as the previous one completes, with no 5 second gap.
However, if we use @Scheduled(cron = "*/5 * * ? * *")
instead, the output becomes:
start 13:22:10, finish 13:22:20, i = 1
start 13:22:25, finish 13:22:35, i = 2
start 13:22:40, finish 13:22:50, i = 3
start 13:22:55, finish 13:22:55, i = 4
start 13:23:00, finish 13:23:00, i = 5
start 13:23:05, finish 13:23:05, i = 6
start 13:23:10, finish 13:23:10, i = 7
start 13:23:15, finish 13:23:15, i = 8
start 13:23:20, finish 13:23:20, i = 9
start 13:23:25, finish 13:23:25, i = 10
There is always a 5 second gap between the jobs. The overlapping jobs are NOT queued and are skipped.
Overlapping jobs are queued for fixedRate and they are skipped for cron as mentioned by @firstmanonmars
If we want to execute the corn schedulers overlapping to each other without waiting, we can use @Async and @EnableAsync as shown below.
@EnableScheduling
@SpringBootApplication
@EnableAsync
public class TaskSchedulerApplication {
public static void main(String[] args) {
SpringApplication.run(TaskSchedulerApplication.class, args);
}
@Bean
public TaskScheduler taskScheduler() {
final ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
return scheduler;
}
}
Scheduler Demo:
@Component
public class DemoScheduler {
@Async
@Scheduled(cron = "*/5 * * * * MON-FRI")
public void startJob() {
System.out.println(String.format("%s - Thread name - %s ",new Date(), Thread.currentThread().getName()));
sleep(6000);
}
private void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Running the code gives the following output which shows that they are running by multiple threads in parallel in an interval of 5 sec:
Thu May 06 09:59:05 IST 2021 - Thread name - task-1
Thu May 06 09:59:10 IST 2021 - Thread name - task-2
Thu May 06 09:59:15 IST 2021 - Thread name - task-3
Thu May 06 09:59:20 IST 2021 - Thread name - task-4
Thu May 06 09:59:25 IST 2021 - Thread name - task-5
Thu May 06 09:59:30 IST 2021 - Thread name - task-6
Thu May 06 09:59:35 IST 2021 - Thread name - task-7
Thu May 06 09:59:40 IST 2021 - Thread name - task-8
Thu May 06 09:59:45 IST 2021 - Thread name - task-1
Thu May 06 09:59:50 IST 2021 - Thread name - task-2
Thu May 06 09:59:55 IST 2021 - Thread name - task-3
By default, execution is blocking and single-threaded, which means they will not run concurrently. If you want jobs to run simultaneously, you can annotate the method as @Async
as well. You may also want to look at the different Executors
.
If you're using fixedDelay
like in the sample question you provided, the next job will only kick off AFTER the current one is over, plus the delay. So if your job takes 10 hours and you have a fixedDelay
of 5000
, the next job will kick off 5 seconds after the 10 hour one.
If you're using fixedRate
then the next scheduled event will be queued up to run, but not skipped, as per the documentation:
If any execution of this task takes longer than its period, then subsequent executions may start late, but will not concurrently execute.
If you are simply using cron
, then the jobs will be queued and executed in turn (similar to fixedRate
). You can test this with a simple method (below is in Groovy, but can use plain Java too):
int i = 0
@Scheduled(cron = '* * * * * * ')
void test() {
if (i < 5)
Thread.sleep(10000)
i += 1
println '------------------------' + i // No values skipped
}
You'll note that every number is printed; e.g. no cycle is ever skipped.