一尘不染

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

java

我想每天早上5点执行某项任务。因此,我决定使用ScheduledExecutorService它,但到目前为止,我已经看到了一些示例,这些示例显示了如何每隔几分钟运行一次任务。

而且我找不到任何示例来说明如何每天在特定时间(上午5点)每天运行任务,并且还考虑了夏时制的事实-

以下是我的代码,每15分钟运行一次-

public class ScheduledTaskExample {
    private final ScheduledExecutorService scheduler = Executors
        .newScheduledThreadPool(1);

    public void startScheduleTask() {
    /**
    * not using the taskHandle returned here, but it can be used to cancel
    * the task, or check if it's done (for recurring tasks, that's not
    * going to be very useful)
    */
    final ScheduledFuture<?> taskHandle = scheduler.scheduleAtFixedRate(
        new Runnable() {
            public void run() {
                try {
                    getDataFromDatabase();
                }catch(Exception ex) {
                    ex.printStackTrace(); //or loggger would be better
                }
            }
        }, 0, 15, TimeUnit.MINUTES);
    }

    private void getDataFromDatabase() {
        System.out.println("getting data...");
    }

    public static void main(String[] args) {
        ScheduledTaskExample ste = new ScheduledTaskExample();
        ste.startScheduleTask();
    }
}

有什么办法,我可以ScheduledExecutorService考虑兼顾夏时制的事实,安排一个任务在每天的凌晨5点运行?

而且TimerTask对于这个还是更好ScheduledExecutorService


阅读 1360

收藏
2020-03-18

共1个答案

一尘不染

与当前的Java SE 8版本一样,它具有出色的日期时间API,与java.time使用java.util.Calendarand相比,可以更轻松地完成这些计算 java.util.Date

  • 使用此新API的日期时间类,即LocalDateTime
  • 使用ZonedDateTime类处理时区特定的计算,包括夏令时问题。你将在此处找到教程和示例。
    现在作为使用你的用例计划任务的示例示例:
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("America/Los_Angeles"));
ZonedDateTime nextRun = now.withHour(5).withMinute(0).withSecond(0);
if(now.compareTo(nextRun) > 0)
    nextRun = nextRun.plusDays(1);

Duration duration = Duration.between(now, nextRun);
long initalDelay = duration.getSeconds();

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);            
scheduler.scheduleAtFixedRate(new MyRunnableTask(),
    initalDelay,
    TimeUnit.DAYS.toSeconds(1),
    TimeUnit.SECONDS);

initalDelay计算问调度延迟在执行TimeUnit.SECONDS。对于此用例,单位毫秒及以下的时差问题似乎可以忽略不计。但是你还是可以使用的duration.toMillis(),并TimeUnit.MILLISECONDS以毫秒为单位处理调度computaions

而且TimerTask对此或ScheduledExecutorService更好吗?

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

从@PaddyD,

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

确实如此,@ PaddyD已经给出了解决方法(给他+1),我提供了一个Java8日期时间API和的工作示例ScheduledExecutorService。使用守护程序线程很危险

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是具有功能的接口execute
  • 在停止时ScheduledExecutorService,请始终awaitTermination在调用后使用shutdown:始终有可能你的任务被卡死/死锁,并且用户将永远等待。
    我给Calender给出的上一个示例只是我提到的一个想法,避免了精确的时间计算和夏时制问题。根据@PaddyD的抱怨更新了解决方案
2020-03-18