【问题标题】:ScheduledExecutorService not executing task with an initialDelay 0ScheduledExecutorService 未执行初始延迟为 0 的任务
【发布时间】:2022-01-24 22:30:17
【问题描述】:

ScheduledExecutorService 在使用scheduleWithFixedDelayscheduleAtFixedRate 时未执行具有0 时间单位的initialDelay 提交的任务。但是当我在上面调用schedule 时它正在执行任务

ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
Runnable runnable = () -> System.out.println("I am a runnable");
scheduledExecutorService.schedule(runnable, 0, TimeUnit.SECONDS);
scheduledExecutorService.shutdown();

输出

I am a runnable

但是当我使用scheduleWithFixedDelayscheduleAtFixedRate 而不是schedule 时,任务没有得到执行。

ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
Runnable runnable = () -> System.out.println("I am a runnable");
scheduledExecutorService.scheduleWithFixedDelay(runnable, 0, 1, TimeUnit.SECONDS);
// or  scheduledExecutorService.scheduleAtFixedRate(runnable, 0, 1, TimeUnit.SECONDS);
scheduledExecutorService.shutdown();

为什么在这种情况下任务没有得到执行?我希望在 initialDelay 设置为 0 时执行此任务

【问题讨论】:

  • 不过,奇怪的是我在scheduleWithFixedDelayscheduleAtFixedRate 中找不到任何异步调用ScheduledThreadPoolExecutor,这可能会导致这种情况发生。如果不是这种情况,那么在返回 API 调用之前执行任务必须有一些开销。
  • 我认为zysaaa 的回答更接近这种情况

标签: java scheduledexecutorservice


【解决方案1】:

您过早关闭了执行程序。这就是为什么其他线程尚未启动的原因。不保证线程会先启动然后关闭方法。

如果您使用 awaitTermination,您将看到输出。

scheduledExecutorService.awaitTermination(1, TimeUnit.MILLISECONDS);

//输出:我是一个runnable

【讨论】:

    【解决方案2】:

    您对ExecutorService#shutdown 的调用将停止任何进一步的任务安排。显然,您的调用发生得如此之快,以至于调度程序永远无法执行计划任务的第一次运行。这是有道理的。

    不太有意义的是为什么对schedule 的调用恰好被安排得足够快以在关机前及时执行其任务,但对scheduleWithFixedDelayscheduleAtFixedRate 的调用却延迟为零没有尽快安排。显然,scheduleWithFixedDelayscheduleAtFixedRate 调用中存在一些开销,这些开销足以让程序在它们有机会进行第一次执行之前退出。

    无论如何,您可以在我的代码版本running live at IdeOne.com 中看到该行为。添加这一行:

    Thread.sleep( 1_000 * 5 ) ;
    

    ...在您关机之前有足够的时间执行某些scheduleWithFixedDelayscheduleAtFixedRate 任务。

    或者通过添加对awaitTermination 的调用来查看相同的效果,如correct Answer by Nguyen 所示。

    【讨论】:

    • @matt 对于重复计划任务,这意味着当前正在进行的任务的执行将完成,但之后不会再安排该任务的重复。否则,我们将无法停止重复的任务。
    • @matt 从理论上讲,我认为 延迟为零 的重复任务将与schedule 调用一样快地执行。但显然不是。显然,在 scheduleWithFixedDelay 和 scheduleAtFixedRate 调用中确实存在一些开销,这些开销足以让程序在它们有机会进行第一次执行之前退出。
    • 我认为@matt 也是对的,用外行的话scheduleWithFixedDelayscheduleAtFixedRate 应该执行初始延迟为0 以保持语义
    • Apparently there is some overhead in the scheduleWithFixedDelay and scheduleAtFixedRate calls, enough overhead that the program exits before they get a chance to make their first executions.你好,Basil,我对这一段有些不同意,我写了一个答案,你能看看
    【解决方案3】:

    背后的原因可以在ScheduledThreadPoolExecutor中找到,也就是Executors.newSingleThreadScheduledExecutor()的实现类

    1。调用schedule时执行任务,

    这是由于executeExistingDelayedTasksAfterShutdown is true by default

        /**
         * False if should cancel non-periodic not-yet-expired tasks on shutdown.
         */
        private volatile boolean executeExistingDelayedTasksAfterShutdown = true;
    

    2。没有为 scheduleWithFixedDelayscheduleAtFixedRate 执行任务

    这是由于continueExistingPeriodicTasksAfterShutdown is false by default

        /**
         * False if should cancel/suppress periodic tasks on shutdown.
         */
        private volatile boolean continueExistingPeriodicTasksAfterShutdown;
    

    我们可以在调度任务之前通过ScheduledThreadPoolExecutor#setExecuteExistingDelayedTasksAfterShutdownPolicyScheduledThreadPoolExecutor#setContinueExistingPeriodicTasksAfterShutdownPolicy覆盖这些参数。

    【讨论】:

      【解决方案4】:

      为什么shutdown被立即调用,schedule可以执行这个任务,而scheduleAtFixedRate不执行这个任务?

      如果使用scheduleAtFixedRate,则此任务为Periodic,默认keepPeriodic策略为false,因此在调用shutdown时,此任务将从工作队列中移除。

      但是使用schedule并没有执行移除任务的逻辑。(isPeriodic为假)

      ScheduledThreadPoolExecutor#onShutdown:

              @Override void onShutdown() {
              BlockingQueue<Runnable> q = super.getQueue();
              boolean keepDelayed =
                  getExecuteExistingDelayedTasksAfterShutdownPolicy();
              boolean keepPeriodic =
                  getContinueExistingPeriodicTasksAfterShutdownPolicy();
              // Traverse snapshot to avoid iterator exceptions
              // TODO: implement and use efficient removeIf
              // super.getQueue().removeIf(...);
              for (Object e : q.toArray()) {
                  if (e instanceof RunnableScheduledFuture) {
                      RunnableScheduledFuture<?> t = (RunnableScheduledFuture<?>)e;
                      if ((t.isPeriodic()
                           ? !keepPeriodic
                           : (!keepDelayed && t.getDelay(NANOSECONDS) > 0))
                          || t.isCancelled()) { // also remove if already cancelled
                          if (q.remove(t))
                              t.cancel(false);
                      }
                  }
              }
              tryTerminate();
          }
      

      所以我认为 Basil Bourque 的回答的解释是不准确的:

      在 scheduleWithFixedDelay 和 scheduleAtFixedRate 调用中显然存在一些开销,这些开销足以使程序在有机会进行首次执行之前退出。

      这不是因为程序在他们有机会进行第一次执行之前就退出了,而是因为线程池中的工作线程没有得到任务(它被删除了,),然后线程池停止了。此时,程序退出。如果任务没有被移除,程序不会立即退出。只有当线程池中的工作线程完成工作队列中的任务时,程序才会退出。 (因为线程池中的工作线程不是守护线程)。

      【讨论】:

      • 是的,我同意,这可能是问题所在,必须执行的任务在工人有机会执行之前已被删除。
      猜你喜欢
      • 1970-01-01
      • 2015-09-14
      • 2020-05-10
      • 2023-01-21
      • 2013-11-11
      • 2014-03-28
      • 1970-01-01
      • 2018-04-23
      • 2018-12-16
      相关资源
      最近更新 更多