Spring 调度注释中固定速率和固定延迟的区别是什么?

我正在使用 Spring 实现计划任务,并且我看到有两种类型的时间配置选项,从上次调用开始,计划再次工作。这两种类型之间有什么区别?

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


@Scheduled(fixedRate = 5000)
public void doJobRate() {
// do anything
}
98544 次浏览
  • FixedRate: 使 Spring 按周期间隔运行任务,即使 最后一个调用可能仍在运行。
  • 定义时,专门控制下一次执行的时间 最后一次行刑结束。

代码:

@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
*/
}

固定延迟 : 专门控制最后一次执行结束时的下一次执行时间。

FixedRate : 使 Spring 定期运行任务,即使最后一次调用可能仍在运行。

“ fixedRate”: 从上一次执行开始等待 X 毫秒,然后再开始下一次执行。如果当前执行超过‘ fixedRate’间隔,下一次执行将排队,这将创建一系列运行的任务,即多个任务实例将运行。

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

输出:

开始: 1
完成: 4秒后1//
开始: 2//立即 w/o 等待1秒,按固定速率指定
完成: 2//4秒后
诸如此类

在下一次执行之前,从上一次执行结束开始等待 X 毫秒。不管当前执行花费了多少时间,下一次执行是在当前执行的结束时间添加了“ fixedTD”间隔之后开始的。它不会排队下一次执行。

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

输出:

开始: 1
完成: 4秒后1//开始: 2//等待1秒钟,如 fixed延迟中指定的那样 完成: 2//4秒后 开始: 1秒后3//
诸如此类

FixedRate: 这用于每 n 毫秒运行调度作业。工作是否已经完成了上一轮并不重要。

它用于在给定的 n 毫秒延迟时间内按顺序运行预定的作业。这意味着,在作业上花费的时间将影响计划作业下一次运行的开始时间。

固定利率例子:

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

我们假设这个任务是在13:00第一次触发的:

  • 第一次运行 -> 13:00:00,工作在13:00:02结束
  • 第二次运行 -> 13:00:05,任务在13:00:08结束
  • 第三次运行 -> 13:00:10,任务在13:00:16完成
  • 第四次运行 -> 13:00:15,任务在13:00:18完成

修正延迟例子:

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

我们假设这个任务是在13:00第一次触发的:

  • 第一次运行 -> 13:00:00,工作在13:00:02结束
  • 第二次运行 -> 13:00:07,任务在13:00:08结束
  • 第三次运行 -> 13:00:13,任务在13:00:16完成
  • 第四次运行 -> 13:00:21,任务在13:00:25结束

何时使用 < em > “ fixedRate”: 如果不希望 fixedRate 超过内存和线程池的大小,则使用 fixedRate 是合适的。如果传入的任务没有快速完成,它可能会以“内存不足异常”结束

什么时候使用 < strong > < em > “ fixed延迟”: 如果每个正在运行的任务彼此相关,并且它们需要在前一个任务完成之前等待,那么 fixedTD 是合适的。如果仔细设置 fixedDelletime,它还会让正在运行的线程有足够的时间在新任务启动之前完成它们的作业

需要澄清的一点是,fixedRate并不意味着执行将以一定的时间间隔开始。

如果一次执行花费了太多的时间(超过固定的速率) ,那么下一次执行将只启动前一次执行完成的 之后,除非提供 @Async@EnableAsync。下面的源代码是 Spring 的 ThreadPoolTaskScheduler实现的一部分,它解释了原因:

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

您可以看到,只有在上一个任务完成(super.run())之后,才会安排下一个任务(schedule())。对于 @Async@EnableAsyncsuper.run()是一个将立即返回的异步函数,因此下一个任务不必等待上一个任务实际完成。

我们可以使用 Spring 的 @Scheduled注释运行计划任务,但是基于属性 fixedDelayfixedRate,执行的性质会发生变化。

fixedDelay属性确保存在 n延迟 任务执行的 finish time和 任务下一次执行的 start time

当我们需要确保任务的一个实例始终运行时,此属性特别有用。对于依赖性工作,这是相当有帮助的。

fixedRate属性在每个 n上运行计划的任务 它不检查以前执行的 任务。

当任务的所有执行都是独立的时候,这非常有用。如果我们不希望超过内存和线程池的大小,那么 fixedRate应该非常方便。

但是,如果传入的任务没有快速完成,它们可能会以“内存不足异常”结束。

对于这些方法的作用,似乎存在着相互矛盾的意见。根据在 Spring 上下文中注册的 taskSchedulerExecutor bean,行为可能会发生变化。我发现@Ammar Akouri 的答案是最接近的。

下面是我在使用 ScheduledThreadPoolExecutor(下面提供的完整测试源代码)时发现的内容

  • fixedDelayfixedRate都不允许并发任务执行
  • fixedDelay将等待以前调用的 结束,然后在将来的某个固定时间安排一个新的调用。因此,它一次不会对多个任务进行排队。
  • fixedRate将在每个周期安排一个新的调用。它将一次排队多个任务(可能是无限制的) ,但绝不会并发执行任务。

样本测试(Kotlin/JUnit) :

class LearningSchedulerTest {


private lateinit var pool: ScheduledExecutorService


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


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


/**
* See: https://stackoverflow.com/questions/24033208/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)
}
}
}
}

FixedTD 属性确保在执行任务的完成时间和下一次执行任务的开始时间之间有 n 毫秒的延迟。

当我们需要确保任务的一个实例始终运行时,此属性特别有用。对于依赖性工作,这是相当有帮助的。

FixedRate 属性以每 n 毫秒的速度运行计划的任务。它不检查任何以前执行的任务。

当任务的所有执行都是独立的时候,这非常有用。如果我们不希望超过内存和线程池的大小,fixedRate 应该非常方便。

但是,如果传入的任务没有快速完成,它们可能会以“内存不足异常”结束。

详情请浏览: Https://www.baeldung.com/spring-scheduled-tasks

一些回答说,如果任务仍在运行,固定速率将运行并行进程。这似乎不是真的。在这篇 Baeldung的文章中,他们说

在这种情况下,从最后一次执行结束到下一次执行开始之间的持续时间是固定的。任务总是等待,直到前一个任务完成。

我亲自测试过了。注意,尽管计划的速率只有3秒,但是代码等待作业完成的时间为5秒。

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

这就产生了

""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