问题描述
我每天早上 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.Calendar
和 java.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
.
- 使用这个新 API 的日期时间类,即 LocalDateTime
- 使用 ZonedDateTime 类来处理时间区域特定计算,包括夏令时问题.您可以在此处找到教程和示例.
- 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?
否: ScheduledExecutorService
似乎比 TimerTask
更好.StackOverflow 已经为您解答.
NO: ScheduledExecutorService
seemingly better than TimerTask
. StackOverflow has already an answer for you.
来自@PaddyD,
您仍然存在需要每年重新启动两次的问题如果您希望它在正确的本地时间运行.以固定速率调度除非您对全年相同的 UTC 时间感到满意,否则不会削减它.
这是真的,@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 functionexecute
.- While stopping
ScheduledExecutorService
, Always useawaitTermination
after invokingshutdown
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 每天在特定时间运行某些任务?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!