我每天早上 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.Calendar
和 java.util.Date
.
- 使用 date time class's i.e. LocalDateTime这个新的 API
- 使用 ZonedDateTime类来处理时区特定的计算,包括夏令时问题。你会发现tutorial and example在这里。
现在作为使用案例安排任务的示例:
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/