且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何使用ScheduledExecutorService在特定时间每天运行某些任务?

更新时间:2023-01-12 18:07:40

与目前的Java SE 8版本一样,它的优秀日期时间API与 java.time 相比,这些计算可以更容易地完成使用 java.util.Calendar java.util.Date

As with the present java SE 8 release with it's excellent date time API with java.time these kind of calculation can be done more easily instead of using java.util.Calendar and java.util.Date.

  • Use date time class's i.e. LocalDateTime of this new API
  • Use ZonedDateTime class to handle Time Zone specific calculation including Daylight Saving issues. You will find tutorial and example here.

现在作为使用您的用例安排任务的示例示例:

Now as a sample example for scheduling a task with your use case:

        LocalDateTime localNow = LocalDateTime.now();
        ZoneId currentZone = ZoneId.of("America/Los_Angeles");
        ZonedDateTime zonedNow = ZonedDateTime.of(localNow, currentZone);
        ZonedDateTime zonedNext5 ;
        zonedNext5 = zonedNow.withHour(5).withMinute(0).withSecond(0);
        if(zonedNow.compareTo(zonedNext5) > 0)
            zonedNext5 = zonedNext5.plusDays(1);

        Duration duration = Duration.between(zonedNow, zonedNext5);
        long initalDelay = duration.getSeconds();

        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);            
        scheduler.scheduleAtFixedRate(new MyRunnableTask(), initalDelay,
                                      24*60*60, TimeUnit.SECONDS);

计算 initalDelay 以询问调度程序延迟 TimeUnit.SECONDS 中的执行。对于此用例,单位毫秒及以下的时差问题似乎可以忽略不计。但您仍然可以使用 duration.toMillis() TimeUnit.MILLISECONDS 来处理以毫秒为单位的调度计算。

The initalDelay is computed to ask the scheduler to delay the execution in TimeUnit.SECONDS. Time difference issues with unit milliseconds and below seems to be negligible for this use case. But you can still make use of duration.toMillis() and TimeUnit.MILLISECONDS for handling the scheduling computaions in milliseconds.


而且TimerTask对于这个或者ScheduledExecutorService更好吗?

And also TimerTask is better for this or ScheduledExecutorService?

NO: ScheduledExecutorService 似乎比 TimerTask 更好。 ***已经为您提供了答案

NO: ScheduledExecutorService seemingly better than TimerTask. *** has already an answer for you.

来自@PaddyD,


您仍然遇到需要的问题如果您希望它在当地时间运行,则每年重启两次
。 scheduleAtFixedRate
不会削减它,除非你对全年相同的UTC时间感到满意。

You still have the issue whereby you need to restart this twice a year if you want it to run at the right local time. scheduleAtFixedRate won't cut it unless you are happy with the same UTC time all year.

因为它是真的, @PaddyD已经给出了一个解决方法(给他+1),我提供了一个带有 ScheduledExecutorService 的Java8日期时间API的工作示例。 使用守护程序线程是危险的

As it is true and @PaddyD already has given a workaround(+1 to him), I am providing a working example with Java8 date time API with ScheduledExecutorService. Using daemon thread is dangerous

class MyTaskExecutor
{
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
    MyTask myTask;
    volatile boolean isStopIssued;

    public MyTaskExecutor(MyTask myTask$) 
    {
        myTask = myTask$;

    }

    public void startExecutionAt(int targetHour, int targetMin, int targetSec)
    {
        Runnable taskWrapper = new Runnable(){

            @Override
            public void run() 
            {
                myTask.execute();
                startExecutionAt(targetHour, targetMin, targetSec);
            }

        };
        long delay = computeNextDelay(targetHour, targetMin, targetSec);
        executorService.schedule(taskWrapper, delay, TimeUnit.SECONDS);
    }

    private long computeNextDelay(int targetHour, int targetMin, int targetSec) 
    {
        LocalDateTime localNow = LocalDateTime.now();
        ZoneId currentZone = ZoneId.systemDefault();
        ZonedDateTime zonedNow = ZonedDateTime.of(localNow, currentZone);
        ZonedDateTime zonedNextTarget = zonedNow.withHour(targetHour).withMinute(targetMin).withSecond(targetSec);
        if(zonedNow.compareTo(zonedNextTarget) > 0)
            zonedNextTarget = zonedNextTarget.plusDays(1);

        Duration duration = Duration.between(zonedNow, zonedNextTarget);
        return duration.getSeconds();
    }

    public void stop()
    {
        executorService.shutdown();
        try {
            executorService.awaitTermination(1, TimeUnit.DAYS);
        } catch (InterruptedException ex) {
            Logger.getLogger(MyTaskExecutor.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

注意:


  • MyTask 是一个功能的界面执行

  • 在停止 ScheduledExecutorService 时,在调用 awaitTermination c>关闭:它总是有可能你的任务卡住/死锁,用户会永远等待。

  • MyTask is an interface with function execute.
  • While stopping ScheduledExecutorService, Always use awaitTermination after invoking shutdown on it: There's always a likelihood your task is stuck / deadlocking and the user would wait forever.

我之前给Calender的例子只是一个想法我提到过,我避免了精确的时间计算和夏令时问题。根据@PaddyD

The previous example I gave with Calender was just an idea which I did mention, I avoided exact time calculation and Daylight saving issues. Updated the solution on per the complain of @PaddyD