就像当前的Java SE 8版本一样,它具有出色的日期时间API——java.time
,这种计算可以更轻松地完成,而不是使用java.util.Calendar
和java.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
来处理以毫秒为单位的调度计算。
在这种情况下,TimerTask
还是ScheduledExecutorService
更好?
否:ScheduledExecutorService
似乎比TimerTask
更好。 StackOverflow已经为您提供了答案。
根据@PaddyD的说法,
如果您希望它以正确的本地时间运行,则每年需要重新启动两次。除非您满意全年使用相同的UTC时间,否则无法使用scheduleAtFixedRate。
由于这是正确的,并且@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
时,始终在调用其上的 shutdown
后使用 awaitTermination
: 您的任务可能会被卡住或死锁,用户会永远等待。
我之前给出的关于日历的示例只是一个想法,我提到了这一点,避免了确切的时间计算和夏令时问题。根据@PaddyD的抱怨更新了解决方案。