25

I have a job that runs once an hour, and I'm using Spring's @scheduled cron to schedule it.

If the job takes more than an hour, I understand from How to prevent overlapping schedules in Spring? that the next job won't kick off while the first job is running.

But does this mean that it WILL kick off after the first job has completed, or has it missed its chance?

If I have one job that takes 10 hours, will all the missed cron jobs queue up and then execute one by one when the first job completes after 10 hours, or will just the first job run?

Thanks!

Bruce
  • 2,406
  • 5
  • 29
  • 35
  • cronjoby are executed in separate threads. So when its time, a new thread is spawned and executed irrespective of previous execution is compete or not. (if you use fixeddelay, then it waits for completion of previously running task) – pvpkiran Jan 05 '18 at 14:00
  • 4
    @pvpkiran Your comment is incorrect. spring scheduled cron jobs will not kick off if a previous iteration is running, regardless of what executor you have - please see the second answer in the answer I linked. – Bruce Jan 05 '18 at 14:42
  • 1
    https://stackoverflow.com/questions/21993464/does-spring-scheduled-annotated-methods-runs-on-different-threads – pvpkiran Jan 05 '18 at 14:45
  • 1
    I believe that the example in Igor's answer contradicts your assertion that "cronjobs are executed in separate threads. So when its time, a new thread is spawned and executed irrespective of previous execution is compete or not." The numbers are returned in sequence which does not fit with your model. – Bruce Jan 05 '18 at 14:53

3 Answers3

23

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.

Igor
  • 33,276
  • 14
  • 79
  • 112
  • Thanks - but you haven't answered my question. – Bruce Jan 05 '18 at 14:39
  • @Bruce Updated. Without code it was a little ambiguous as I thought you were asking about fixedDelay's/fixedRate's interaction w/cron. – Igor Jan 05 '18 at 14:44
  • Ah ok thanks - your edit does answer the question. Thanks! – Bruce Jan 05 '18 at 14:45
  • The answer below https://stackoverflow.com/a/56889523/1527469 seems to contradict the cron scenario, when it says "jobs are NOT queued"--which doesn't in itself imply whether the next counter is incremented it is, but according to that answer there's no possibility of a memory leak in some queue, if I'm not mistaken. – awgtek Mar 09 '22 at 20:08
14

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.

firstmanonmars
  • 149
  • 1
  • 3
2

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