What's the difference between fixed rate and fixed delay in Spring Scheduled annotation?
Asked Answered
M

10

81

I am implementing scheduled tasks using Spring, and I see there are two types of config options for time that schedule work again from the last call. What is the difference between these two types?

 @Scheduled(fixedDelay = 5000)
 public void doJobDelay() {
     // do anything
 }

 @Scheduled(fixedRate = 5000)
 public void doJobRate() {
     // do anything
 }
Mormon answered 9/8, 2016 at 5:9 Comment(0)
C
70
  • fixedRate : makes Spring run the task on periodic intervals even if the last invocation may still be running.
  • fixedDelay : specifically controls the next execution time when the last execution finishes.

In code:

@Scheduled(fixedDelay=5000)
public void updateEmployeeInventory(){
    System.out.println("employee inventory will be updated once only the last updated finished ");
    /**
     * add your scheduled job logic here
     */
}


@Scheduled(fixedRate=5000)
public void updateEmployeeInventory(){
    System.out.println("employee inventory will be updated every 5 seconds from prior updated has stared, regardless it is finished or not");
    /**
     * add your scheduled job logic here
     */
}
Catalogue answered 9/8, 2016 at 6:13 Comment(7)
in each method i using Thread.sleep(5000) to waiting 5s, however i don't see what differentMormon
exactly the same, I believe @Sabrinasabsay explained that correctlyHemeralopia
The difference is that if "updateEmployeeInventory" takes more than 5 seconds with fixedRate you will end up executing in 2 different threads 2 concurrent executions of the method, with fixedDelay Spring start to "count" the 5 seconds after the end of the previous executionBeene
This answer is misleading, see below answer which is correct.Anyone
@gaber84 That's actually wrong. Check fifman's answer below for this specific use case.Stephanistephania
@RanyAlbegWein you are right, my 3 years old assertion should be valid only when Async and EnableAsync are provided. In defense of the past myself it is a very bad practice to have a fixedRate that with time can become less than the execution time of the scheduled method.Beene
The statement "even if the last invocation may still be running" is incorrect. The scheduler doesn't allow parallel invocations of the job. If the invocation exceeds the fixedRate, the scheduler will wait until it completes and then fires the next execution immediately. See also this answer and this answer.Joinville
S
45

"fixedRate" : waits for X millis from the start of previous execution before starting next execution. If current execution exceeds 'fixedRate' interval, the next execution is queued and this will create a series of tasks running ie multiple instances of tasks will be running.

private static int i = 0;

@Scheduled(initialDelay=1000, fixedRate=1000)
public void testScheduling() throws InterruptedException {
    System.out.println("Started : "+ ++i);
    Thread.sleep(4000);
    System.out.println("Finished : "+ i);
}

Output:

Started : 1
Finished : 1 // after 4 seconds
Started : 2 // immediately w/o waiting for 1 sec as specified in fixed rate
Finished : 2 // after 4 seconds
and so on

"fixedDelay" : waits for X millis from the end of previous execution before starting next execution. Doesn't matter how much time current execution is taking, the next execution is started after adding 'fixedDelay' interval to end time of current execution. It will not queue next execution.

private static int i = 0;

@Scheduled(initialDelay=1000, fixedDelay=1000)
public void testScheduling() throws InterruptedException {
    System.out.println("Started : "+ ++i);
    Thread.sleep(4000);
    System.out.println("Finished : "+ i);
}

Output:

Started : 1
Finished : 1 // after 4 seconds Started : 2 // waits for 1 second as specified in fixedDelay Finished : 2 // after 4 seconds Started : 3 // after 1 second
and so on

Sabrinasabsay answered 3/6, 2017 at 4:29 Comment(1)
> "If current execution exceeds 'fixedRate' interval, the next execution is queued, but only next one." This appears to be false when running on a ScheduledThreadPoolExecutor. What I'm seeing in my tests is that the executor queues invocations at each intervalHypsometer
D
28

fixedRate: This is used to run the scheduled jobs in every n milliseconds. In case, the job takes more than defined fixedRate, the next iteration is added to a queue which is triggered right after the previous run finishes.

fixedDelay: It is used to run the scheduled job sequentially with the given n milliseconds delay time between turns. Which means, the time spent on the job will affect the start time of the next run of scheduled job.

fixedRate Example:

@Scheduled(fixedRate = 5000)
public void runJobWithFixedRate() {
...
}

Let's assume the job is triggered at 13:00:00 for the first time:

  • 1st run -> 13:00:00, job finishes at 13:00:02
  • 2nd run -> 13:00:05, job finishes at 13:00:08
  • 3rd run -> 13:00:10, job finishes at 13:00:16
  • 4th run -> 13:00:15, job finishes at 13:00:18

fixedDelay Example:

@Scheduled(fixedDelay = 5000)
public void runJobWithFixedDelay() {
...
}

Let's assume the job is triggered at 13:00:00 for the first time:

  • 1st run -> 13:00:00, job finishes at 13:00:02
  • 2nd run -> 13:00:07, job finishes at 13:00:08
  • 3rd run -> 13:00:13, job finishes at 13:00:16
  • 4th run -> 13:00:21, job finishes at 13:00:25

When to use "fixedRate": fixedRate is appropriate if it is not expected to exceed the size of the memory and the thread pool. If the incoming tasks do not finish quick, it may end up with "Out of Memory exception"

When to use "fixedDelay": If every running task is relevant to each other and they need to wait before the previous one finishes, fixedDelay is suitable. If fixedDelay time is set carefully, it will also let the running threads enough time to finish their jobs before the new task starts

Diahann answered 14/6, 2017 at 11:16 Comment(2)
The statement "It does not matter whether the job has already finished its previous turn or not" is incorrect. The scheduler doesn't allow parallel runs of the job. If the job exceeds the fixedRate, the scheduler will wait until it completes and then fires the next execution immediately. See also this answer and this answer.Joinville
Yep, tested it now @g00glen00b, you are right, changed my sentence there, thanks!Diahann
T
9

One thing which should be clarified is that fixedRate does not mean executions will start with a certain time interval.

If one execution cost too much time (more than the fixed rate), the next execution will only start AFTER the previous one finishes, unless @Async and @EnableAsync are provided. The following source codes which are part of Spring's ThreadPoolTaskScheduler implementation explain why:

@Override
public void run() {
    Date actualExecutionTime = new Date();
    super.run();
    Date completionTime = new Date();
    synchronized (this.triggerContextMonitor) {
        this.triggerContext.update(this.scheduledExecutionTime, actualExecutionTime, completionTime);
        if (!this.currentFuture.isCancelled()) {
            schedule();
        }
    }
}

You can see that only after the previous task is finished (super.run()), the next task is scheduled (schedule()). With @Async and @EnableAsync, super.run() is an async function which will return immediately, thus the next task does not have to wait for the previous one to actually finish.

Torrey answered 4/4, 2018 at 8:37 Comment(0)
S
6

We can run a scheduled task using Spring’s @Scheduled annotation but based on the properties fixedDelay and fixedRate the nature of execution changes.

The fixedDelay property makes sure that there is a delay of n millisecond between the finish time of an execution of a task and the start time of the next execution of the task.

This property is specifically useful when we need to make sure that only one instance of the task runs all the time. For dependent jobs, it is quite helpful.

The fixedRate property runs the scheduled task at every n millisecond. It doesn’t check for any previous executions of the task.

This is useful when all executions of the task are independent. If we don’t expect to exceed the size of the memory and the thread pool, fixedRate should be quite handy.

But, if the incoming tasks do not finish quickly, it’s possible they end up with “Out of Memory exception”.

Sundew answered 18/3, 2019 at 15:47 Comment(0)
B
5

Fixed Delay : specifically controls the next execution time when the last execution finishes.

Fixed Rate : makes Spring run the task on periodic intervals even if the last invocation may be still running.

Biscuit answered 9/8, 2016 at 6:25 Comment(2)
This is not true about fixed-rate, it will waitPeerage
there are 3 videos about it - youtube.com/… - it seems it is not that easy :)Necessaries
H
4

There seems to be conflicting advice on what these methods do. Maybe the behavior can change depending on the taskScheduler or Executor bean registered with the spring context. I found @Ammar Akouri's answer to be the most close.

Here's what I found when using a ScheduledThreadPoolExecutor (full test source provided below)

  • neither fixedDelay nor fixedRate allow concurrent task executions
  • fixedDelay will wait for the end of a previous invocation, then schedule a new inovcation at a fixed amount of time in the future. It will hence not queue up more than one task at a time.
  • fixedRate will schedule a new invocation every period. It will queue up more than one task at a time (potentially unbounded) but will never execute tasks concurrently.

Sample Test (Kotlin/JUnit):

class LearningSchedulerTest {

    private lateinit var pool: ScheduledExecutorService

    @Before
    fun before() {
      pool = Executors.newScheduledThreadPool(2)
    }

    @After
    fun after() {
      pool.shutdown()
    }

    /**
     * See: https://mcmap.net/q/260429/-how-to-prevent-overlapping-schedules-in-spring
     *
     * The documentation claims: If any execution of this task takes longer than its period, then subsequent executions may start late, but will not concurrently execute.
     * https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledThreadPoolExecutor.html#scheduleAtFixedRate-java.lang.Runnable-long-long-java.util.concurrent.TimeUnit-
     */

    @Test
    fun `scheduleAtFixedRate schedules at fixed rate`() {
      val task = TaskFixture( initialSleep = 0)

      pool.scheduleAtFixedRate({task.run()}, 0, 10, TimeUnit.MILLISECONDS )

      Thread.sleep(15)
      Assert.assertEquals(2, task.invocations.get())

      Thread.sleep(10)
      Assert.assertEquals(3, task.invocations.get())

      Thread.sleep(10)
      // 1 initial and 3 periodic invocations
      Assert.assertEquals(4, task.invocations.get())
    }

    @Test
    fun `scheduleAtFixedRate catches up on late invocations`() {
      val task = TaskFixture(initialSleep = 30)

      pool.scheduleAtFixedRate({task.run()}, 0, 10, TimeUnit.MILLISECONDS )

      Thread.sleep(15) // we see no concurrent invocations
      Assert.assertEquals(1, task.invocations.get())

      Thread.sleep(10) // still no concurrent invocations
      Assert.assertEquals(1, task.invocations.get())

      Thread.sleep(10)

      // 1 initial and 3 periodic invocations
      Assert.assertEquals(4, task.invocations.get())
    }

    @Test
    fun `scheduleWithFixedDelay schedules periodically`() {
      val task = TaskFixture( initialSleep = 0)

      pool.scheduleWithFixedDelay({task.run()}, 0, 10, TimeUnit.MILLISECONDS )

      Thread.sleep(35)

      // 1 initial and 3 periodic invocations
      Assert.assertEquals(4, task.invocations.get())
    }

    @Test
    fun `scheduleWithFixedDelay does not catch up on late invocations`() {
      val task = TaskFixture( initialSleep = 30)

      pool.scheduleWithFixedDelay({task.run()}, 0, 10, TimeUnit.MILLISECONDS )

      Thread.sleep(35)

      // 1 initial invocation, no time to wait the specified 10ms for a second invocation
      Assert.assertEquals(1, task.invocations.get())
    }

    class TaskFixture(val initialSleep: Long) {
      var invocations = AtomicInteger()

      fun run() {
        invocations.incrementAndGet()
        if (invocations.get() == 1){
          Thread.sleep(initialSleep)
        }
      }
    }
}
Hypsometer answered 23/4, 2019 at 14:54 Comment(2)
Is there a way to allow for concurrent execution?Glucose
There is: you have to use Scheduled with Async, and make sure your executor is not single threaded.Glucose
O
2

The fixedDelay property makes sure that there is a delay of n millisecond between the finish time of an execution of a task and the start time of the next execution of the task.

This property is specifically useful when we need to make sure that only one instance of the task runs all the time. For dependent jobs, it is quite helpful.

The fixedRate property runs the scheduled task at every n millisecond. It doesn't check for any previous executions of the task.

This is useful when all executions of the task are independent. If we don't expect to exceed the size of the memory and the thread pool, fixedRate should be quite handy.

But, if the incoming tasks do not finish quickly, it's possible they end up with “Out of Memory exception”.

For more details visit: https://www.baeldung.com/spring-scheduled-tasks

Orthogenic answered 30/3, 2020 at 15:17 Comment(0)
P
2

Several answered have said that fixed rate will run parallel processes if tasks are still running. This seems not true. In this baeldung article they say

In this case, the duration between the end of the last execution and the start of the next execution is fixed. The task always waits until the previous one is finished.

I've tested it myself. Notice the code waits 5 seconds for the job to finish even though the scheduled rate is only 3 seconds.

  AtomicInteger runCount = new AtomicInteger(0);

  /** Sleeps for 5 seconds but pops every 3 seconds */
  @Scheduled(fixedRate = 3000)
  public void runTransactionBillingJob() throws InterruptedException {
    log.info("{}: Popping", runCount);
    Thread.sleep(5000);
    log.info("{}: Done", runCount);
    runCount.incrementAndGet();
  }

Which produces

""10:52:26.003 [pls-scheduled-task-pool-1] INFO  c.p.c.s.i.InvoiceSettingsServiceImpl.runTransactionBillingJob 38  - 0: Done
""10:52:26.004 [pls-scheduled-task-pool-1] INFO  c.p.c.s.i.InvoiceSettingsServiceImpl.runTransactionBillingJob 36  - 1: Popping
""10:52:31.015 [pls-scheduled-task-pool-1] INFO  c.p.c.s.i.InvoiceSettingsServiceImpl.runTransactionBillingJob 38  - 1: Done
""10:52:31.017 [pls-scheduled-task-pool-1] INFO  c.p.c.s.i.InvoiceSettingsServiceImpl.runTransactionBillingJob 36  - 2: Popping
""10:52:36.023 [pls-scheduled-task-pool-1] INFO  c.p.c.s.i.InvoiceSettingsServiceImpl.runTransactionBillingJob 38  - 2: Done
""10:52:36.024 [pls-scheduled-task-pool-1] INFO  c.p.c.s.i.InvoiceSettingsServiceImpl.runTransactionBillingJob 36  - 3: Popping
""10:52:41.032 [pls-scheduled-task-pool-1] INFO  c.p.c.s.i.InvoiceSettingsServiceImpl.runTransactionBillingJob 38  - 3: Done
""10:52:41.033 [pls-scheduled-task-pool-1] INFO  c.p.c.s.i.InvoiceSettingsServiceImpl.runTransactionBillingJob 36  - 4: Popping
Peerage answered 15/3, 2022 at 15:3 Comment(0)
S
0
int i = 0, j = 0;

/**
 * Fixed Delay: Always waits until previous one is completed. 
 * Fixed delay is between the end of previous and start of the current task.
 *
 * @throws InterruptedException
 */
@Scheduled(fixedDelay = 1000)
public void scheduleFixedDelayTask() throws InterruptedException {
    logger.info("Fixed delay task - " + (++i) + " " + sdf.format(new Date()));
}

2023-09-20 17:05:56.378  INFO 26352 --- [   scheduling-1] c.d.springboot.demo.SchedulerService     : Fixed delay task - 1 2023-09-20 17:05:56
2023-09-20 17:05:56.380  INFO 26352 --- [           main] c.d.s.d.SpringBootSchedulerApplication   : Started SpringBootSchedulerApplication in 2.185 seconds (JVM running for 2.569)
2023-09-20 17:05:58.393  INFO 26352 --- [   scheduling-1] c.d.springboot.demo.SchedulerService     : Job finished - 1 2023-09-20 17:05:58
2023-09-20 17:05:59.400  INFO 26352 --- [   scheduling-1] c.d.springboot.demo.SchedulerService     : Fixed delay task - 2 2023-09-20 17:05:59
2023-09-20 17:06:01.414  INFO 26352 --- [   scheduling-1] c.d.springboot.demo.SchedulerService     : Job finished - 2 2023-09-20 17:06:01
2023-09-20 17:06:02.419  INFO 26352 --- [   scheduling-1] c.d.springboot.demo.SchedulerService     : Fixed delay task - 3 2023-09-20 17:06:02
2023-09-20 17:06:04.432  INFO 26352 --- [   scheduling-1] c.d.springboot.demo.SchedulerService     : Job finished - 3 2023-09-20 17:06:04

/**
 * Initial Delay: The task will be executed the first time after the initial delay.
 * Start 2000ms after application start
 */
@Scheduled(initialDelay = 2000, fixedDelay = 1000)
public void scheduleFixedDelayWithInitialDelayTask() throws InterruptedException {
    logger.info("Fixed delay with initial delay task - " + (++i) + " " + sdf.format(new Date()));
}

2023-09-20 17:06:50.123  INFO 7856 --- [           main] c.d.s.d.SpringBootSchedulerApplication   : Started SpringBootSchedulerApplication in 1.986 seconds (JVM running for 2.376)
2023-09-20 17:06:52.121  INFO 7856 --- [   scheduling-1] c.d.springboot.demo.SchedulerService     : Fixed delay with initial delay task - 1 2023-09-20 17:06:52
2023-09-20 17:06:54.137  INFO 7856 --- [   scheduling-1] c.d.springboot.demo.SchedulerService     : Job finished - 1 2023-09-20 17:06:54
2023-09-20 17:06:55.145  INFO 7856 --- [   scheduling-1] c.d.springboot.demo.SchedulerService     : Fixed delay with initial delay task - 2 2023-09-20 17:06:55
2023-09-20 17:06:57.148  INFO 7856 --- [   scheduling-1] c.d.springboot.demo.SchedulerService     : Job finished - 2 2023-09-20 17:06:57
2023-09-20 17:06:58.159  INFO 7856 --- [   scheduling-1] c.d.springboot.demo.SchedulerService     : Fixed delay with initial delay task - 3 2023-09-20 17:06:58
2023-09-20 17:07:00.162  INFO 7856 --- [   scheduling-1] c.d.springboot.demo.SchedulerService     : Job finished - 3 2023-09-20 17:07:00

/**
 * Fixed Rate: Run tasks in fixed intervals.
 * Tasks not run in parallel by default, so waits until previous one is completed.
 *
 * @throws InterruptedException
 */

@Scheduled(initialDelay = 2000, fixedRate = 1000)
public void scheduleFixedRateTask() throws InterruptedException {
    logger.info("Fixed rate task - " + (++i) + " " + sdf.format(new Date()));
    Thread.sleep(2000);
    logger.info("Job finished - " + (++j) + " " + sdf.format(new Date()));
}

2023-09-20 16:59:24.178  INFO 92 --- [   scheduling-1] c.d.springboot.demo.SchedulerService     : Fixed rate task - 1 2023-09-20 16:59:24
2023-09-20 16:59:26.189  INFO 92 --- [   scheduling-1] c.d.springboot.demo.SchedulerService     : Job finished - 1 2023-09-20 16:59:26
2023-09-20 16:59:26.190  INFO 92 --- [   scheduling-1] c.d.springboot.demo.SchedulerService     : Fixed rate task - 2 2023-09-20 16:59:26
2023-09-20 16:59:28.198  INFO 92 --- [   scheduling-1] c.d.springboot.demo.SchedulerService     : Job finished - 2 2023-09-20 16:59:28
2023-09-20 16:59:28.198  INFO 92 --- [   scheduling-1] c.d.springboot.demo.SchedulerService     : Fixed rate task - 3 2023-09-20 16:59:28
2023-09-20 16:59:30.207  INFO 92 --- [   scheduling-1] c.d.springboot.demo.SchedulerService     : Job finished - 3 2023-09-20 16:59:30

/**
 * Fixed Rate with Async: Run tasks in fixed intervals in parellel.
 *
 * @throws InterruptedException
 */
@Async
@Scheduled(initialDelay = 2000,fixedRate = 1000)
public void scheduleFixedRateAsyncTask() throws InterruptedException {
    logger.info("Fixed rate async task - " + (++i) + " " + sdf.format(new Date()));
    Thread.sleep(2000);
    logger.info("Job finished - " + (++j) + " " + sdf.format(new Date()));
}

2023-09-20 17:00:50.464  INFO 23856 --- [         task-1] c.d.springboot.demo.SchedulerService     : Fixed rate async task - 1 2023-09-20 17:00:50
2023-09-20 17:00:51.441  INFO 23856 --- [         task-2] c.d.springboot.demo.SchedulerService     : Fixed rate async task - 2 2023-09-20 17:00:51
2023-09-20 17:00:52.450  INFO 23856 --- [         task-3] c.d.springboot.demo.SchedulerService     : Fixed rate async task - 3 2023-09-20 17:00:52
2023-09-20 17:00:52.465  INFO 23856 --- [         task-1] c.d.springboot.demo.SchedulerService     : Job finished - 1 2023-09-20 17:00:52
2023-09-20 17:00:53.442  INFO 23856 --- [         task-4] c.d.springboot.demo.SchedulerService     : Fixed rate async task - 4 2023-09-20 17:00:53
2023-09-20 17:00:53.457  INFO 23856 --- [         task-2] c.d.springboot.demo.SchedulerService     : Job finished - 2 2023-09-20 17:00:53
2023-09-20 17:00:54.449  INFO 23856 --- [         task-5] c.d.springboot.demo.SchedulerService     : Fixed rate async task - 5 2023-09-20 17:00:54
2023-09-20 17:00:54.464  INFO 23856 --- [         task-3] c.d.springboot.demo.SchedulerService     : Job finished - 3 2023-09-20 17:00:54
2023-09-20 17:00:55.443  INFO 23856 --- [         task-4] c.d.springboot.demo.SchedulerService     : Job finished - 4 2023-09-20 17:00:55
2023-09-20 17:00:55.444  INFO 23856 --- [         task-6] c.d.springboot.demo.SchedulerService     : Fixed rate async task - 6 2023-09-20 17:00:55
2023-09-20 17:00:56.440  INFO 23856 --- [         task-7] c.d.springboot.demo.SchedulerService     : Fixed rate async task - 7 2023-09-20 17:00:56
2023-09-20 17:00:56.456  INFO 23856 --- [         task-5] c.d.springboot.demo.SchedulerService     : Job finished - 5 2023-09-20 17:00:56
2023-09-20 17:00:57.447  INFO 23856 --- [         task-6] c.d.springboot.demo.SchedulerService     : Job finished - 6 2023-09-20 17:00:57
Substrate answered 20/9, 2023 at 11:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.