在Java中的ManagedSchedulerExecutorService类

13 浏览
0 Comments

在Java中的ManagedSchedulerExecutorService类

我想每天早上5点运行一个特定的任务。因此,我决定使用ScheduledExecutorService,但到目前为止,我只看到了每隔几分钟运行任务的示例。

我找不到任何示例显示如何在每天的特定时间(早上5点)运行任务,并考虑到夏令时的因素。

下面是我的代码,它将每隔15分钟运行一次:

public class ScheduledTaskExample {
    private final ScheduledExecutorService scheduler = Executors
        .newScheduledThreadPool(1);
    public void startScheduleTask() {
    /**
    * 不使用此处返回的taskHandle,但它可以用于取消任务,或者检查任务是否完成(对于重复任务,这将没有多大用处)
    */
    final ScheduledFuture taskHandle = scheduler.scheduleAtFixedRate(
        new Runnable() {
            public void run() {
                try {
                    getDataFromDatabase();
                }catch(Exception ex) {
                    ex.printStackTrace(); //或者最好使用日志记录器
                }
            }
        }, 0, 15, TimeUnit.MINUTES);
    }
    private void getDataFromDatabase() {
        System.out.println("获取数据...");
    }
    public static void main(String[] args) {
        ScheduledTaskExample ste = new ScheduledTaskExample();
        ste.startScheduleTask();
    }
}

是否有办法使用ScheduledExecutorService在每天早上5点运行任务,并考虑到夏令时的因素?

此外,TimerTask对于这个任务来说更好还是ScheduledExecutorService更好?

0
0 Comments

在Java SE 8版本中,引入了新的日期时间API java.time,使得进行这类计算变得更加容易,而不再需要使用java.util.Calendarjava.util.Date

现在,以一个示例来展示如何使用新的日期时间API调度一个任务:

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

那么TimerTask比ScheduledExecutorService更好吗?

不是ScheduledExecutorService似乎比TimerTask更好。在StackOverflow上已经有了答案

从以下引用中可知,使用scheduleAtFixedRate存在两次年度重新启动的问题,如果你希望任务以正确的本地时间运行,则无法满足要求。

由于这是真实的,并且已经给出了解决方法(+1给他),我将提供一个使用Java 8日期时间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(){
            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:因为任务可能陷入死锁,用户可能会无限等待。

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

感谢提供建议,你能详细解释一下intDelayInHour的含义吗?这样我就可以在早上5点运行我的任务了。

aDate的目的是什么?

但是,如果你在HH:mm时启动它,任务将在05:mm运行,而不是在早上5点运行。它也没有解决夏令时的问题,正如OP所要求的那样。如果你立即在整点后启动它,或者如果你满意在5点到6点之间的任何时间,或者如果你不介意在时钟变化后的两天中午夜重启应用程序,那么可以使用这种方法...

你仍然需要每年两次重新启动,如果你希望任务在正确的本地时间运行,scheduleAtFixedRate不能满足这个要求。

为什么下面的示例(第二个)会触发n次执行或直到时间截止?代码不是应该每天只触发一次任务吗?

也许这是一个不同的问题,我们能使用JobScheduler实现相同的结果吗?

如果计算机休眠,这个示例解决方案似乎不起作用。初始延迟会被冻结,直到计算机唤醒,然后任务将在错误的时间运行。

如果你尝试在午夜之后(或者在午夜)运行一个运行非常快的任务,这个示例解决方案仍然无法正常工作。它有至少两个问题 - 它可能在午夜之前触发,因为计划执行器不是精确的,当触发时,它会一直运行,通常是999次 - 每毫秒运行一次,直到下一毫秒发生。仍然在寻找合适的解决方案...

最好的方法是运行一个快速(不准确的)定时器(我选择每4秒)并在当前时间超过nextRun时触发事件,然后更新nextRun以进行下一次调用。类似于这样:private void scheduleNext(){LocalDateTime now=LocalDateTime.now();nextRun=now.truncatedTo(ChronoUnit.DAYS).plusHours(5);if(nextRun.compareTo(now)<=0)nextRun=nextRun.plus(1,ChronoUnit.DAYS);}

0
0 Comments

在这段代码中,使用了Executors.newScheduledThreadPool(1)创建了一个只有一个线程的线程池scheduler。然后通过scheduleAtFixedRate()方法来设置定时任务的执行时间和间隔时间。

然而,这段代码存在一些问题。首先,使用LocalDateTime.now()获取当前时间,在存在夏令时(Daylight Savings Time, DST)等时间调整的情况下,会导致时间不准确。因为LocalDateTime类没有考虑时区或UTC偏移量的上下文。

其次,使用了一个"魔法数字"1440来表示一天的分钟数,不够直观和可读。建议使用TimeUnit.DAYS.toMinutes(1)来代替。

此外,代码中创建的线程池只有一个线程,这在某些情况下可能会导致问题,比如在存在夏令时转换时,可能需要重启两次才能运行在正确的本地时间。

为了解决这些问题,可以使用ZonedDateTime类来获取带有时区信息的当前时间,并使用Duration类来计算时间间隔。同时,可以考虑创建一个具有多个线程的线程池,以便能够并行执行多个任务。

下面是修改后的代码示例:

scheduler = Executors.newScheduledThreadPool(2);
ZonedDateTime now = ZonedDateTime.now(ZoneId.systemDefault());
ZonedDateTime midnight = now.with(LocalTime.MIDNIGHT).plusDays(1);
Duration initialDelay = Duration.between(now, midnight);
Duration interval = Duration.ofDays(1);
scheduler.scheduleAtFixedRate(this, initialDelay.getSeconds(), interval.getSeconds(), TimeUnit.SECONDS);

通过使用ZonedDateTimeDuration类,我们可以更准确地获取当前时间和计算时间间隔。同时,通过使用具有多个线程的线程池,可以并行执行多个任务,提高效率。

总之,这段代码的问题在于使用了LocalDateTime类来获取当前时间,没有考虑时区和夏令时调整,同时线程池只有一个线程。通过使用ZonedDateTimeDuration类来解决时区和时间间隔的问题,以及创建一个具有多个线程的线程池来提高效率。

0
0 Comments

问题的出现原因:

该问题的出现是因为在Java 8之前的版本中,没有提供类似于Java 8中的ScheduledExecutorService接口来实现定时任务的执行。

解决方法:

为了解决这个问题,可以使用上述给出的代码。代码中定义了一个DailyRunnerDaemon类,通过传入Calendar对象和Runnable对象来实现定时任务的执行。代码中使用了Timer类来调度任务的执行,并且通过递归调用startTimer方法实现了定时执行。

如果需要使用ScheduledExecutorService接口来实现定时任务的执行,可以将startTimer方法修改如下:

private void startTimer() {
   Executors.newSingleThreadExecutor().schedule(new Runnable() {
      public void run() {
         Thread.currentThread().setName(runThreadName);
         dailyTask.run();
         startTimer();
      }
   }, getNextRunTime().getTime() - System.currentTimeMillis(),
   TimeUnit.MILLISECONDS);
}

需要注意的是,使用ScheduledExecutorService接口需要添加相应的依赖库,并且在代码中可能需要添加停止方法来停止定时任务的执行,以防止应用程序无法终止。

通过上述方法,可以在Java 8之前的版本中实现定时任务的执行。通过使用Timer类或者ScheduledExecutorService接口,可以实现定时执行任务的功能,从而满足特定的业务需求。

0