如何使用 ScheduledExecutorService 每天在特定时间运行某些任务? [英] How to run certain task every day at a particular time using ScheduledExecutorService?

查看:44
本文介绍了如何使用 ScheduledExecutorService 每天在特定时间运行某些任务?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am trying to run a certain task everyday at 5 AM in the morning. So I decided to use ScheduledExecutorService for this but so far I have seen examples which shows how to run task every few minutes.

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

And I am not able to find any example which shows how to run a task every day at a particular time (5 AM) in the morning and also considering the fact of daylight saving time as well -

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

Below is my code which will run every 15 minutes -

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 点运行的任务?

Is there any way, I can schedule a task to run every day 5 AM in the morning using ScheduledExecutorService considering the fact of daylight saving time as well?

还有 TimerTask 更适合这个还是 ScheduledExecutorService?

And also TimerTask is better for this or ScheduledExecutorService?

推荐答案

与当前的 java SE 8 版本一样,它具有出色的日期时间 API 和 java.time,这些类型的计算可以完成更多轻松代替使用 java.util.Calendarjava.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:

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 来处理以毫秒为单位的调度计算.

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?

否: ScheduledExecutorService 似乎比 TimerTask 更好.StackOverflow 已经为您解答.

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

来自@PaddyD,

您仍然存在需要每年重新启动两次的问题如果您希望它在正确的本地时间运行.以固定速率调度除非您对全年相同的 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 是一个带有execute 函数的接口.
  • 在停止 ScheduledExecutorService 时,总是在调用 shutdown 之后使用 awaitTermination:总是有可能你的任务被卡住/死锁而用户会永远等待.
  • 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

这篇关于如何使用 ScheduledExecutorService 每天在特定时间运行某些任务?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆