我每天凌晨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(); } }
有没有办法,我可以安排一项任务,每天早上5点运行,同时ScheduledExecutorService
考虑夏令时的事实?
也TimerTask
就是这个或更好ScheduledExecutorService
?
与目前的Java SE 8版本一样,它具有出色的日期时间API,java.time
这种计算可以更轻松地完成,而不是使用 java.util.Calendar
和java.util.Date
.
使用此新API的日期时间类即LocalDateTime
使用ZonedDateTime类处理特定于时区的计算,包括夏令时问题.你会在这里找到教程和例子.
现在作为使用您的用例计划任务的示例示例:
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
处理调度计算.
而TimerTask对于这个还是ScheduledExecutorService更好?
不: ScheduledExecutorService
看似好过TimerTask
.StackOverflow已经为您解答了.
来自@PaddyD,
如果您希望在当地正确的时间运行,您仍然需要每年重启两次.scheduleAtFixedRate不会削减它,除非你对全年相同的UTC时间感到满意.
事实并且@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
,总是awaitTermination
在调用shutdown
它之后使用:总是有可能你的任务卡住/死锁,用户会永远等待.
我之前用Calender给出的例子只是我提到的一个想法,我避免了精确的时间计算和夏令时问题.根据@PaddyD的抱怨更新了解决方案
如果您没有能够使用Java 8的奢侈品,以下将满足您的需求:
public class DailyRunnerDaemon { private final Runnable dailyTask; private final int hour; private final int minute; private final int second; private final String runThreadName; public DailyRunnerDaemon(Calendar timeOfDay, Runnable dailyTask, String runThreadName) { this.dailyTask = dailyTask; this.hour = timeOfDay.get(Calendar.HOUR_OF_DAY); this.minute = timeOfDay.get(Calendar.MINUTE); this.second = timeOfDay.get(Calendar.SECOND); this.runThreadName = runThreadName; } public void start() { startTimer(); } private void startTimer(); { new Timer(runThreadName, true).schedule(new TimerTask() { @Override public void run() { dailyTask.run(); startTimer(); } }, getNextRunTime()); } private Date getNextRunTime() { Calendar startTime = Calendar.getInstance(); Calendar now = Calendar.getInstance(); startTime.set(Calendar.HOUR_OF_DAY, hour); startTime.set(Calendar.MINUTE, minute); startTime.set(Calendar.SECOND, second); startTime.set(Calendar.MILLISECOND, 0); if(startTime.before(now) || startTime.equals(now)) { startTime.add(Calendar.DATE, 1); } return startTime.getTime(); } }
它不需要任何外部库,并将占用夏令时.只需传递您想要将任务作为Calendar
对象运行的时间,并将任务作为一个Runnable
.例如:
Calendar timeOfDay = Calendar.getInstance(); timeOfDay.set(Calendar.HOUR_OF_DAY, 5); timeOfDay.set(Calendar.MINUTE, 0); timeOfDay.set(Calendar.SECOND, 0); new DailyRunnerDaemon(timeOfDay, new Runnable() { @Override public void run() { try { // call whatever your daily task is here doHousekeeping(); } catch(Exception e) { logger.error("An error occurred performing daily housekeeping", e); } } }, "daily-housekeeping");
注意,计时器任务在守护程序线程中运行,不建议执行任何IO.如果需要使用User线程,则需要添加另一个取消计时器的方法.
如果必须使用a ScheduledExecutorService
,只需将startTimer
方法更改为以下内容:
private void startTimer() { Executors.newSingleThreadExecutor().schedule(new Runnable() { Thread.currentThread().setName(runThreadName); dailyTask.run(); startTimer(); }, getNextRunTime().getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS); }
我不确定该行为,但您可能需要一个停止方法,shutdownNow
如果您沿着ScheduledExecutorService
路线走,则会调用,否则当您尝试停止时,您的应用程序可能会挂起.
您是否考虑过使用Quartz Scheduler之类的东西?这个库有一个机制,用于使用类似cron的表达式来调度每天在设定的时间段运行的任务(看看CronScheduleBuilder
).
一些示例代码(未测试):
public class GetDatabaseJob implements InterruptableJob { public void execute(JobExecutionContext arg0) throws JobExecutionException { getFromDatabase(); } } public class Example { public static void main(String[] args) { JobDetails job = JobBuilder.newJob(GetDatabaseJob.class); // Schedule to run at 5 AM every day ScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0 0 5 * * ?"); Trigger trigger = TriggerBuilder.newTrigger(). withSchedule(scheduleBuilder).build(); Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); scheduler.scheduleJob(job, trigger); scheduler.start(); } }
预先有一些工作,你可能需要重写你的工作执行代码,但它应该可以让你更好地控制你的工作运行方式.如果需要,也可以更容易地更改计划.
在Java 8中:
scheduler = Executors.newScheduledThreadPool(1); //Change here for the hour you want ----------------------------------.at() Long midnight=LocalDateTime.now().until(LocalDate.now().plusDays(1).atStartOfDay(), ChronoUnit.MINUTES); scheduler.scheduleAtFixedRate(this, midnight, 1440, TimeUnit.MINUTES);
Java8:
我的升级版本从顶部答案:
修复Web应用程序服务器不想停止的情况,因为线程池具有空闲线程
没有递归
使用您的自定义本地时间运行任务,就我而言,它是白俄罗斯,明斯克
/** * Execute {@link AppWork} once per day. * <p> * Created by aalexeenka on 29.12.2016. */ public class OncePerDayAppWorkExecutor { private static final Logger LOG = AppLoggerFactory.getScheduleLog(OncePerDayAppWorkExecutor.class); private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); private final String name; private final AppWork appWork; private final int targetHour; private final int targetMin; private final int targetSec; private volatile boolean isBusy = false; private volatile ScheduledFuture<?> scheduledTask = null; private AtomicInteger completedTasks = new AtomicInteger(0); public OncePerDayAppWorkExecutor( String name, AppWork appWork, int targetHour, int targetMin, int targetSec ) { this.name = "Executor [" + name + "]"; this.appWork = appWork; this.targetHour = targetHour; this.targetMin = targetMin; this.targetSec = targetSec; } public void start() { scheduleNextTask(doTaskWork()); } private Runnable doTaskWork() { return () -> { LOG.info(name + " [" + completedTasks.get() + "] start: " + minskDateTime()); try { isBusy = true; appWork.doWork(); LOG.info(name + " finish work in " + minskDateTime()); } catch (Exception ex) { LOG.error(name + " throw exception in " + minskDateTime(), ex); } finally { isBusy = false; } scheduleNextTask(doTaskWork()); LOG.info(name + " [" + completedTasks.get() + "] finish: " + minskDateTime()); LOG.info(name + " completed tasks: " + completedTasks.incrementAndGet()); }; } private void scheduleNextTask(Runnable task) { LOG.info(name + " make schedule in " + minskDateTime()); long delay = computeNextDelay(targetHour, targetMin, targetSec); LOG.info(name + " has delay in " + delay); scheduledTask = executorService.schedule(task, delay, TimeUnit.SECONDS); } private static long computeNextDelay(int targetHour, int targetMin, int targetSec) { ZonedDateTime zonedNow = minskDateTime(); ZonedDateTime zonedNextTarget = zonedNow.withHour(targetHour).withMinute(targetMin).withSecond(targetSec).withNano(0); if (zonedNow.compareTo(zonedNextTarget) > 0) { zonedNextTarget = zonedNextTarget.plusDays(1); } Duration duration = Duration.between(zonedNow, zonedNextTarget); return duration.getSeconds(); } public static ZonedDateTime minskDateTime() { return ZonedDateTime.now(ZoneId.of("Europe/Minsk")); } public void stop() { LOG.info(name + " is stopping."); if (scheduledTask != null) { scheduledTask.cancel(false); } executorService.shutdown(); LOG.info(name + " stopped."); try { LOG.info(name + " awaitTermination, start: isBusy [ " + isBusy + "]"); // wait one minute to termination if busy if (isBusy) { executorService.awaitTermination(1, TimeUnit.MINUTES); } } catch (InterruptedException ex) { LOG.error(name + " awaitTermination exception", ex); } finally { LOG.info(name + " awaitTermination, finish"); } } }