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

标签 java timertask background-thread scheduledexecutorservice

我每天早上 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

最佳答案

与当前的 java SE 8 版本一样,它具有出色的日期时间 API 和 java.time,这些计算可以更轻松地完成,而不是使用 java.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);

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

And also TimerTask is better for this or ScheduledExecutorService?

否: ScheduledExecutorService 貌似比 TimerTask 好。 StackOverflow has already an answer for you .

来自@PaddyD,

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 的工作示例。 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的接口(interface)。
  • 在停止 ScheduledExecutorService 时,在调用 shutdown 后始终使用 awaitTermination:您的任务总是有可能卡住/死锁并且用户将永远等待。

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

关于java - 如何使用 ScheduledExecutorService 每天在特定时间运行特定任务?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20387881/

相关文章:

ios - 如何在 swift 4 中解决此问题 `Realm accessed from incorrect thread.`

ios - 反向地理编码并将结果保存在 CoreData 中

java - 使用 Intent 设置 ImageView 颜色(另一个 Activity )

java - 如何使用扫描仪从命令行读取文件

Java - Timer.cancel() 与 TimerTask.cancel()

java - 在 Wicket 应用程序中同时运行计时器

java - Jobrunr 与非基于 spring 的 java web 应用程序

java - adb连接错误:EOF (new in android )

android - "App Lock"应用程序如何在内部工作?

multithreading - Xamarin.Forms 后台线程