简体   繁体   English

ScheduledExecutorService 未按预期运行

[英]ScheduledExecutorService not behaving as expected

I'm trying to execute a task at midnight after a fixed amount of days using ScheduleExecutorService .我正在尝试使用ScheduleExecutorService在固定天数后的午夜执行任务。 My method runs inside of my tomcat 8 and looks like this:我的方法在我的 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);
    }

Now when the method is first run it seems like is not executed as specifed by delay and interval .现在,当该方法第一次运行时,它似乎没有按照delayinterval Eg when i set delay=60 and interval=5 in my stacktrace it looks like this:例如,当我在堆栈跟踪中设置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 
...

So the intervals somehow are becoming shorter and shorter over time.因此,随着时间的推移,间隔不知何故变得越来越短。 What is going on here?这里发生了什么? Is there a problem in my method?我的方法有问题吗?

Now when the method is first run it seems like is not executed as specified by delay and interval. 现在,当第一次运行该方法时,似乎未按延迟和间隔指定的那样执行。 Eg when i set delay=60 and interval=5 in my stacktrace it looks like this... 例如,当我在我的stacktrace中设置delay = 60和interval = 5时,它看起来像这样...

I try to be very explicit with my time variables. 我试图对我的时间变量非常明确。 In this case, you should be dealing with milliseconds so delayMillis , intervalMillis , aIntervalMillis , etc. should be in your code. 在这种情况下,您应该处理毫秒,因此delayMillisintervalMillisaIntervalMillis等应包含在您的代码中。 If they are seconds then use delaySecs , etc. but you will need to multiple them by 1000 when you pass them to the scheduleAtFixedRate(...) method which is expecting millis. 如果它们是秒,则使用delaySecs等,但是当您将它们传递给期望毫秒数的scheduleAtFixedRate(...)方法时 ,需要将它们乘以1000。

So the intervals somehow are becoming shorter and shorter over time. 因此,间隔随时间以某种方式变得越来越短。 What is going on here? 这里发生了什么? Is there a problem in my method? 我的方法有问题吗?

What's probably going on is that the task is trying to schedule it every 5 milliseconds so the random delays are just showing you how long your doTask() takes to run. 可能发生的情况是该任务试图每5毫秒进行一次调度,因此随机延迟只是向您显示doTask()运行多长时间。 If you want to set them to 60 and 5 seconds respectively then you should use 60000 and 5000 instead. 如果要将它们分别设置为60和5 ,则应改用60000和5000。

When i try that and my schedule() method is running i get a message from my eclipse (neon 3) saying tomcat is not responding and my tomcat is not shutting down properly. 当我尝试这样做并且我的schedule()方法正在运行时,我从日食(霓虹灯3)中收到一条消息,提示tomcat没有响应,并且tomcat无法正常关闭。

Not sure about this but I suspect that your tomcat is waiting for your scheduled task to finish which is never will on its own. 对此不确定,但我怀疑您的雄猫正在等待您计划的任务完成,而这永远不会自己完成。 You could use a ThreadFactory and create a daemon thread instead which the JVM will not wait for when it shuts down. 您可以使用ThreadFactory并创建一个守护程序线程,而JVM在关闭时将不等待。 Note that if it is a daemon thread then it might get terminated right in the middle of running. 请注意,如果它是守护程序线程,则可能会在运行过程中被终止。

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

Also, you should call scheduler.shutdown() on your thread-pool after you submit your fixed task. 另外,提交固定任务后,应在线程池上调用scheduler.shutdown() It will continue to run but no other jobs will be submitted. 它将继续运行,但是不会提交其他作业。 That's a good pattern. 这是一个很好的模式。

How can I stop my execution task in a proper way? 如何以适当的方式停止执行任务?

The daemon thread mode about may be "proper" in your case but you can also cancel it. 守护程序线程模式在您的情况下可能是“正确的”,但是您也可以取消它。 The scheduler.scheduleAtFixedRate(...) method returns a ScheduledFuture object that you can call cancel(...) on to stop it. scheduler.scheduleAtFixedRate(...)方法返回一个ScheduledFuture对象,您可以对其调用cancel(...)来停止它。 Once your doTask() finishes it won't be scheduled again. 一旦doTask()完成,就不会再安排它了。 You can also call cancel(true) on it to set the interrupt flag on the thread but your code is going to have to handle that specifically. 您还可以在其上调用cancel(true)来在线程上设置中断标志,但是您的代码将必须专门处理该标志。

Of course you are going to need to detect that your JVM is shutting down so you can cancel your task. 当然,您将需要检测到JVM正在关闭,以便您可以取消任务。 You could set a shutdown hook which is a bit of a hack. 您可以设置一个关闭钩子,这有点麻烦。 Maybe there is some way for tomcat to notify that it's coming down? 也许tomcat有某种方法可以通知它即将崩溃?

the intervals somehow are becoming shorter and shorter over time随着时间的推移,间隔不知何故变得越来越短

We need to understand how ScheduledExecutorService:scheduleAtFixedRate is supposed to work.我们需要了解ScheduledExecutorService:scheduleAtFixedRate应该如何工作。 According to docs, https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html#scheduleAtFixedRate , we can conclude the following:根据文档https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html#scheduleAtFixedRate ,我们可以得出以下结论:

  1. The time of each run is pre-determined.每次运行的时间都是预先确定的。
  2. This pre-calculated time may not tally when the previous run of the same task takes longer than point no 1. In such case, the next run will happen immediately after the longer running previous run has finished.当同一任务的前一次运行时间比点 1 长时,这个预先计算的时间可能不符合。在这种情况下,下一次运行将在运行时间更长的前一次运行完成后立即发生。
  3. Same task can never run concurrently;同一个任务永远不能同时运行; even if the expected start time of the next run has exceeded.即使已超过下一次运行的预期开始时间。 As mentioned by point no 2, the next run will wait till the previous longer run finishes.正如第 2 点所提到的,下一次运行将等到上一次较长的运行完成。

Now, coming to your case, you had expected to see your task getting run at equal intervals.现在,谈到您的情况,您曾期望看到您的任务以相等的时间间隔运行。 But, instead the intervals between them are getting shorter with each run.但是,每次运行它们之间的间隔反而越来越短。 This could be due to any previous run that took considerable longer time, piling up other run(s) which have exceeded their expected start time.这可能是由于之前的任何运行花费了相当长的时间,堆积了超过预期开始时间的其他运行。 Further piled up runs ( doTask method) finish quickly.进一步堆积的运行( doTask方法)很快完成。 So, all the piled up runs are running at closer intervals.因此,所有堆积的运行都以更近的间隔运行。 If you really want the task to run at equal intervals, you could instead use ScheduledExecutorService: scheduleWithFixedDelay如果您真的希望任务以相等的时间间隔运行,则可以改用ScheduledExecutorService: scheduleWithFixedDelay

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM