如何使用定时执行服务每天在特定时间运行特定任务?

我试图每天凌晨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();
    }
}

有没有办法,我可以安排一个任务每天凌晨5点运行,同时考虑到夏令时的事实?ScheduledExecutorService

而且也比这更好还是?TimerTaskScheduledExecutorService


答案 1

与目前的java SE 8版本一样,它具有出色的日期时间API,这些计算可以更容易地完成,而不是使用和。java.timejava.util.Calendarjava.util.Date

现在作为使用您的用例计划任务的示例示例:

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 initialDelay = duration.getSeconds();

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

计算为要求调度程序延迟 在 中的执行。对于此用例,单位毫秒及以下的时间差问题似乎可以忽略不计。但是,您仍然可以在毫秒内使用和处理调度计算。initialDelayTimeUnit.SECONDSduration.toMillis()TimeUnit.MILLISECONDS

而且TimerTask更适合这个或RespectrandExecutorService?

NO:似乎比 .StackOverflow已经为您提供了答案ScheduledExecutorServiceTimerTask

从@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
  • 在停止时,始终在调用它后使用:您的任务总是有可能被卡住/死锁,用户会永远等待。ScheduledExecutorServiceawaitTerminationshutdown

我用Calender给出的上一个例子只是一个我确实提到过的想法,我避免了精确的时间计算和夏令时问题。根据@PaddyD的投诉更新了解决方案


答案 2

在Java 8中:

scheduler = Executors.newScheduledThreadPool(1);

//Change here for the hour you want ----------------------------------.at()       
Long midnight=LocalDateTime.now().until(LocalDate.now().plusDays(1).atStartOfDay(), ChronoUnit.MINUTES);
scheduler.scheduleAtFixedRate(this, midnight, 1440, TimeUnit.MINUTES);

推荐