ScheduledExecutorService的行为不符合预期

时间:2017-08-10 16:05:13

标签: java multithreading tomcat scheduled-tasks scheduledexecutorservice

我正在尝试使用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 
...

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

1 个答案:

答案 0 :(得分:0)

  

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

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

  

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

可能发生的是该任务正在尝试每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通知它正在下降?