【问题标题】:ScheduledExecutorService not behaving as expectedScheduledExecutorService 未按预期运行
【发布时间】:2018-01-18 23:44:49
【问题描述】:

我正在尝试使用ScheduleExecutorService 在固定天数后的午夜执行一项任务。我的方法在我的 tomcat 8 内部运行,如下所示:

public void schedule (int aInterval)
    {
        String timezone = TimeZone.getDefault().getID();
        ZoneId z = ZoneId.of(timezone);
        ZonedDateTime now = ZonedDateTime.now( z );

        LocalDate tomorrow = now.toLocalDate().plusDays(1);    
        ZonedDateTime tomorrowStart = tomorrow.atStartOfDay( z );
        Duration duration = Duration.between( now , tomorrowStart );
        long millisecondsUntilTomorrow = duration.toMillis();
        long interval;

        if (aInterval * 24 * 60 * 60 > Long.MAX_VALUE)
        {
            interval = Long.MAX_VALUE;
        }
        else
        {
            interval = aInterval * 24 * 60 * 60;
        }

        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, new ThreadFactory() {
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                // allow the JVM to kill the scheduled task
                thread.setDaemon(true);
                return thread;
            });            
        scheduler.scheduleAtFixedRate(new Runnable()
            {
                public void run() {
                    System.out.println(String.format("schedule::run() at  %1$Td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS \n", System.currentTimeMillis() ) );

                    doTask();
                }
            }, 
            delay,
            interval, 
            TimeUnit.SECONDS);
    }

现在,当第一次运行该方法时,它似乎没有按照delayinterval 的指定执行。例如。当我在堆栈跟踪中设置 delay=60interval=5 时,它看起来像这样:

...
schedule::run() at  10.08.2017 17:57:09 
schedule::run() at  10.08.2017 17:57:15 
schedule::run() at  10.08.2017 17:57:21 
schedule::run() at  10.08.2017 17:57:27 
schedule::run() at  10.08.2017 17:57:27 
schedule::run() at  10.08.2017 17:57:33 
schedule::run() at  10.08.2017 17:57:33 
schedule::run() at  10.08.2017 17:57:34 
...

因此,时间间隔不知何故随着时间的推移变得越来越短。这里发生了什么?我的方法有问题吗?

【问题讨论】:

    标签: java multithreading tomcat scheduled-tasks scheduledexecutorservice


    【解决方案1】:

    现在,当第一次运行该方法时,它似乎没有按照延迟和间隔指定的方式执行。例如。当我在堆栈跟踪中设置 delay=60 和 interval=5 时,它看起来像这样......

    我尝试对我的时间变量非常明确。在这种情况下,您应该处理毫秒,因此 delayMillisintervalMillisaIntervalMillis 等应该在您的代码中。如果它们是秒,则使用 delaySecs 等,但是当您将它们传递给期望为毫秒的 scheduleAtFixedRate(...) method 时,您需要将它们乘以 1000。

    因此,时间间隔不知何故随着时间的推移变得越来越短。这里发生了什么?我的方法有问题吗?

    可能发生的情况是该任务尝试每 5 毫秒安排一次,因此随机延迟只是显示您的 doTask() 需要多长时间才能运行。如果您想将它们分别设置为 60 和 5 ,那么您应该使用 60000 和 5000。

    当我尝试这样做并且我的 schedule() 方法正在运行时,我从我的 Eclipse(霓虹灯 3)收到一条消息,说 tomcat 没有响应并且我的 tomcat 没有正确关闭。

    对此不确定,但我怀疑您的 tomcat 正在等待您的计划任务完成,而这永远不会自行完成。您可以使用 ThreadFactory 并创建一个守护线程,而不是 JVM 在关闭时不会等待它。请注意,如果它是一个守护线程,那么它可能会在运行过程中终止。

    scheduler = Executors.newScheduledThreadPool(1, new ThreadFactory() {
        public Thread newThread(Runnable r) {
           Thread thread = new Thread(r);
           // allow the JVM to kill the scheduled task
           thread.setDaemon(true);
           return thread;
        }
    });
    

    此外,您应该在提交固定任务后在线程池上调用scheduler.shutdown()。它将继续运行,但不会提交其他作业。这是一个很好的模式。

    我怎样才能以正确的方式停止我的执行任务?

    在您的情况下,守护程序线程模式可能是“正确的”,但您也可以取消它。 scheduler.scheduleAtFixedRate(...) 方法返回一个 ScheduledFuture 对象,您可以调用 cancel(...) 来停止它。一旦您的doTask() 完成,它将不会再次被安排。您也可以在其上调用cancel(true) 以在线程上设置中断标志,但您的代码必须专门处理它。

    当然,您需要检测到您的 JVM 正在关闭,以便您可以取消您的任务。你可以设置一个关机钩子,这有点小技巧。也许tomcat有什么方法可以通知它正在关闭?

    【讨论】:

    • 感谢线程工厂的想法,它工作正常。但是定时功能仍然不起作用。无论我使用什么间隔时间单位,一段时间后该函数都会变得疯狂并越来越频繁地执行可运行文件。 doTask() 花费的时间不应超过 2-3 秒,但即使我将间隔设置为 60000 毫秒,问题也会继续发生。
    【解决方案2】:

    随着时间的推移,间隔变得越来越短

    我们需要了解ScheduledExecutorService:scheduleAtFixedRate 应该如何工作。根据文档https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html#scheduleAtFixedRate,我们可以得出以下结论:

    1. 每次运行的时间都是预先确定的。
    2. 当同一任务的上一次运行花费的时间超过第 1 点时,此预先计算的时间可能不计算在内。在这种情况下, 下一次运行将在运行时间较长的上一次运行之后立即发生 已结束。
    3. 同一任务不能同时运行;即使已超过下一次运行的预期开始时间。如第 2 点所述,下一次运行将等到 之前较长的运行结束。

    现在,谈到您的情况,您曾期望看到您的任务以相等的间隔运行。但是,相反,它们之间的间隔随着每次运行而变短。这可能是由于任何先前的运行花费了相当长的时间,堆积了超过预期开始时间的其他运行。进一步堆积运行(doTask 方法)快速完成。因此,所有堆积的运行都以更近的间隔运行。如果您真的希望任务以相等的间隔运行,您可以改用ScheduledExecutorService: scheduleWithFixedDelay

    【讨论】:

      猜你喜欢
      • 2014-11-12
      • 1970-01-01
      • 2020-06-28
      • 2012-02-18
      • 2012-06-14
      • 2019-03-03
      • 2012-09-21
      • 2014-07-19
      • 2014-12-08
      相关资源
      最近更新 更多