scheduleAtFixedRate与scheduleWithFixedDelay的区别

152

scheduleAtFixedRatescheduleWithFixedDelay方法在ScheduledExecutorService中的主要区别是什么?

scheduler.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
        System.out.println("scheduleAtFixedRate:    " + new Date());
    }
}, 1, 3L , SECONDS);

scheduler.scheduleWithFixedDelay(new Runnable() {
    @Override
    public void run() {
        System.out.println("scheduleWithFixedDelay: " + new Date());
    }
}, 1, 3L , SECONDS);

他们的打印时间完全相同,看起来它们是在完全相同的间隔内执行。

8个回答

255

尝试在您的run()方法中添加Thread.sleep(1000);调用...基本上这是基于前一次执行结束和(逻辑上) 开始之间的区别。

例如,假设我安排一个定时器以固定的速率每小时响铃一次,并且每次响铃时我喝一杯咖啡,需要10分钟。假设从午夜开始,我会有:

00:00: Start making coffee
00:10: Finish making coffee
01:00: Start making coffee
01:10: Finish making coffee
02:00: Start making coffee
02:10: Finish making coffee

如果我使用一个固定的延迟一小时的时间表,那么我将会有:

00:00: Start making coffee
00:10: Finish making coffee
01:10: Start making coffee
01:20: Finish making coffee
02:20: Start making coffee
02:30: Finish making coffee

你需要哪一个取决于你的任务。


29
如果制作一杯咖啡需要超过一个小时,那么在固定速率方案中会发生什么? - Brett VanderVeen
5
我认为这取决于具体的执行者。它将按时调度 - 但是否执行取决于此执行者是否有可用线程。我建议您尝试在不同情况下了解其工作原理。 - Jon Skeet
13
根据文档,“如果该任务的任何一次执行超过了其周期,那么后续的执行可能会延迟开始,但不会同时执行。”换句话说,符合规范的实现不会在前一个执行完成之前允许下一个执行。 - M. Justin
你能提供类似这个输出(coffee)的可运行代码给像我这样的新手吗? - MuneshSingh
@MuneshSingh:此问题并非要求解释固定频率调度和固定延迟调度之间的区别。无论如何,您都不会自己实现它-您将使用内置的执行程序。 - Jon Skeet

75

可视化scheduleAtFixedRate方法的时间序列。如果上一个执行比周期长,下一个执行将立即开始。否则,它会在周期时间后开始。

scheduleAtFixedRate方法的时间序列

scheduleWithFixedDelay方法的时间序列。下一个执行将在上一个执行结束和下一个执行开始之间的延迟时间之后开始,而不管其执行时间

scheduleWithFixedDelay方法的时间序列

希望能对您有所帮助


我无法理解scheduleAtFixedRate时间序列图中提到的“extra”单词。 - MuneshSingh
1
@MuneshSingh 它的意思是显示任务的执行时间比预定时间长,所以它需要“额外”时间,下一个执行立即开始。 - Viorel
@Viorel 感谢您的澄清。这是否意味着“周期”不是两次连续执行之间的固定时间延迟? - MuneshSingh
1
@MuneshSingh 这个时间间隔是固定的,但一旦它过去了,它不会停止当前任务,只是在这次运行和下次运行之间没有延迟。如果你想创建一个“超时”,你可能想保留 Future 并在不同的执行器中取消它。简单来说,它的意思是_在“period”时间过去后尽快启动第一次执行和下一次执行_。 - Viorel

22
< p > scheduleAtFixedRate() 方法每个周期都创建一个新的任务并将其提交给执行程序,无论上一个任务是否完成

另一方面,scheduleWithFixedDelay() 方法在上一个任务完成后创建一个新的任务。


1
你两次写了 scheduleAtFixedRate :) - Vlad

5
如果您阅读Java Doc,会更加清晰明了。
ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)创建并执行一个周期性的操作,该操作在给定的初始延迟后首次启用,并在给定的时间段内随后启用;也就是说,执行将在initialDelay之后开始,然后是initialDelay+period,然后是initialDelay + 2 * period等等。
ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)创建并执行一个周期性的操作,该操作在给定的初始延迟后首次启用,并在上一次执行终止与下一次执行开始之间具有给定的延迟。

2

如果第一个线程执行时间过长,在给定的时间内没有结束,那么第二个连续线程将不会在第一个任务完成后立即开始,JVM会决定下一个任务何时执行。因此,scheduleAtFixedRate 方法存在一个注意点。

我认为这可以帮助你选择方法,因为由此我遇到了很大的问题。


1
什么?JVM会决定?这是什么意思?根据文档,可执行程序确实不会与自身并发执行,但是它是由执行器决定的,该执行器可以是自定义的,也可以是标准的ScheduledThreadPoolExecutor(后者有良好定义的行为)。 - Ordous
我在我的应用程序中发现了类似的问题,我设置了15分钟的间隔,但第一个任务没有在15分钟内完成,而是花费了15.30秒,因此第二个任务并没有立即开始,有时会在5分钟后开始,有时会在8分钟后开始,我不知道是否可以控制这种行为,因为这不是标准行为。 - user1047873
那听起来像是教科书式的任务排队。 - Ordous
是的,它只是意味着您执行器中的所有线程都已经忙于做某些事情,而您的任务被放入待办事项队列中。(注意您需要通过查看该队列或查看执行器线程正在做什么来确认此操作)。如何控制这取决于您拥有的执行器类型。您可能希望为此特定任务创建一个单独的1线程执行器,那么它就不会等待任何东西。或者给您当前的执行器更多的线程。或者更改其策略。 - Ordous

1
我能理解你的前提,但你的结论并不完全正确。根据这个教程,以下是一个很好且相当完整的解释,用于理解这两者之间的差异。 scheduleAtFixedRate(Runnable, long initialDelay, long period, TimeUnit timeunit) 此方法定期安排执行任务。任务将在initialDelay之后第一次执行,然后每当period到期时都会再次执行。如果给定任务的任何执行引发异常,则不再执行该任务。如果没有抛出异常,则任务将继续执行,直到ScheduledExecutorService被关闭。如果任务执行的时间比其计划执行之间的时间段长,则下一次执行将在当前执行完成后开始。预定任务将不会被多个线程同时执行。 scheduleWithFixedDelay(Runnable, long initialDelay, long period, TimeUnit timeunit) 此方法与scheduleAtFixedRate()非常相似,只是对期间的解释不同。在scheduleAtFixedRate()方法中,周期被解释为从上一次执行开始到下一次执行开始之间的延迟。但是,在此方法中,周期被解释为从上一次执行结束到下一次执行开始之间的延迟。因此,延迟是在完成执行之间而不是在执行开始之间。

0

让我们写一个简单的程序:

import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

var time = 0L
var start = System.currentTimeMillis()
val executor = Executors.newScheduledThreadPool(1)
executor.scheduleWithFixedDelay({
    if (time >= 12_000L) {
        executor.shutdown()
    } else {
        Thread.sleep(2000L)
        val now = System.currentTimeMillis()
        time += now - start
        System.out.println("Total $time delay ${now - start}\n")
        start = now
    }
}, 0L, 1000L, TimeUnit.MILLISECONDS)

看看结果:

| scheduleWithFixedDelay |   scheduleAtFixedRate  |
|:----------------------:|:----------------------:|
| Total 2001 delay 2001  | Total 2003 delay 2003  |
| Total 5002 delay 3001  | Total 4004 delay 2001  |
| Total 8003 delay 3001  | Total 6004 delay 2000  |
| Total 11003 delay 3000 | Total 8004 delay 2000  |
| Total 14003 delay 3000 | Total 10005 delay 2001 |
|          ---           | Total 12005 delay 2000 |

注意执行时间比等待时间长

scheduleWithFixedDelay 保持延迟
scheduleAtFixedRate 移除延迟


-2
scheduledExecutorService.scheduleAtFixedRate(() -> {
        System.out.println("runnable start"); try { Thread.sleep(5000);  System.out.println("runnable end");} catch
     (InterruptedException e) { // TODO Auto-generated catch block
      e.printStackTrace(); }}, 2, 7, TimeUnit.SECONDS);



     scheduledExecutorService.scheduleWithFixedDelay(() -> {
     System.out.println("runnable start"); try { Thread.sleep(5000); System.out.println("runnable end");} catch
     (InterruptedException e) { // TODO Auto-generated catch block
     e.printStackTrace(); } }, 2, 7, TimeUnit.SECONDS);

只需执行它,你就会知道区别。谢谢


1
请简要解释代码如何解决问题。 :) - Yash

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接