Я пытаюсь выполнять определенную задачу каждый день в 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
.Теперь в качестве примера для планирования задачи с вашим вариантом использования:
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
для обработки вычислений расписания в миллисекундах.НЕТ:
ScheduledExecutorService
вроде бы лучше, чемTimerTask
. У StackOverflow уже есть ответ для вас .От @PaddyD,
Поскольку это правда, и @PaddyD уже дал обходной путь (+1 ему), я предоставляю рабочий пример с API даты и времени Java8 с
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
: всегда есть вероятность, что ваша задача зависнет / заблокируется, и пользователь будет ждать вечно.Предыдущий пример, который я привел с календарем, был просто идеей, о которой я упоминал, я избегал точного расчета времени и проблем с переходом на летнее время. Обновил решение по жалобе @PaddyD
источник
intDelayInHour
означает, что я буду выполнять свою задачу в 5 утра?scheduleAtFixedRate
не будет сокращать его, если вы не будете довольны одним и тем же временем UTC в течение всего года.В 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);
источник
TimeUnit.DAYS.toMinutes(1)
вместо «магического числа» 1440.Если у вас нет возможности использовать 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");
NB, задача таймера выполняется в потоке демона, который не рекомендуется для выполнения операций ввода-вывода. Если вам нужно использовать поток пользователя, вам нужно будет добавить еще один метод, который отменяет таймер.
Если вам нужно использовать
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
маршруту, иначе ваше приложение может зависнуть, когда вы попытаетесь его остановить.источник
new Timer(runThreadName, true)
).Думали ли вы об использовании чего-то вроде 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(); } }
Впереди немного больше работы, и вам может потребоваться переписать код выполнения задания, но это должно дать вам больше контроля над тем, как вы хотите, чтобы задание выполнялось. Кроме того, при необходимости будет проще изменить расписание.
источник
Java8:
Моя версия обновления сверху ответ:
/** * 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"); } } }
источник
У меня была аналогичная проблема. Мне пришлось запланировать кучу задач, которые нужно было выполнить в течение дня
ScheduledExecutorService
. Это было решено одной задачей, начинающейся в 3:30 утра, и планированием всех других задач относительно его текущего времени . И перенёс себя на следующий день в 3:30 утра.В этом сценарии переход на летнее время больше не является проблемой.
источник
Вы можете использовать простой синтаксический анализ даты, если время дня раньше, давайте начнем завтра:
String timeToStart = "12:17:30"; SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss"); SimpleDateFormat formatOnlyDay = new SimpleDateFormat("yyyy-MM-dd"); Date now = new Date(); Date dateToStart = format.parse(formatOnlyDay.format(now) + " at " + timeToStart); long diff = dateToStart.getTime() - now.getTime(); if (diff < 0) { // tomorrow Date tomorrow = new Date(); Calendar c = Calendar.getInstance(); c.setTime(tomorrow); c.add(Calendar.DATE, 1); tomorrow = c.getTime(); dateToStart = format.parse(formatOnlyDay.format(tomorrow) + " at " + timeToStart); diff = dateToStart.getTime() - now.getTime(); } ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); scheduler.scheduleAtFixedRate(new MyRunnableTask(), TimeUnit.MILLISECONDS.toSeconds(diff) , 24*60*60, TimeUnit.SECONDS);
источник
Просто чтобы добавить ответ Виктора .
Я бы порекомендовал добавить проверку, чтобы увидеть,
midnight
превышает ли переменная (в его случае long )1440
. Если это так, я бы пропустил.plusDays(1)
, иначе задача будет запущена только послезавтра.Я сделал это просто так:
Long time; final Long tempTime = LocalDateTime.now().until(LocalDate.now().plusDays(1).atTime(7, 0), ChronoUnit.MINUTES); if (tempTime > 1440) { time = LocalDateTime.now().until(LocalDate.now().atTime(7, 0), ChronoUnit.MINUTES); } else { time = tempTime; }
источник
truncatedTo()
Следующий пример работает для меня
public class DemoScheduler { public static void main(String[] args) { // Create a calendar instance Calendar calendar = Calendar.getInstance(); // Set time of execution. Here, we have to run every day 4:20 PM; so, // setting all parameters. calendar.set(Calendar.HOUR, 8); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.AM_PM, Calendar.AM); Long currentTime = new Date().getTime(); // Check if current time is greater than our calendar's time. If So, // then change date to one day plus. As the time already pass for // execution. if (calendar.getTime().getTime() < currentTime) { calendar.add(Calendar.DATE, 1); } // Calendar is scheduled for future; so, it's time is higher than // current time. long startScheduler = calendar.getTime().getTime() - currentTime; // Setting stop scheduler at 4:21 PM. Over here, we are using current // calendar's object; so, date and AM_PM is not needed to set calendar.set(Calendar.HOUR, 5); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.AM_PM, Calendar.PM); // Calculation stop scheduler long stopScheduler = calendar.getTime().getTime() - currentTime; // Executor is Runnable. The code which you want to run periodically. Runnable task = new Runnable() { @Override public void run() { System.out.println("test"); } }; // Get an instance of scheduler final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); // execute scheduler at fixed time. scheduler.scheduleAtFixedRate(task, startScheduler, stopScheduler, MILLISECONDS); } }
Справка: https://chynten.wordpress.com/2016/06/03/java-scheduler-to-run-every-day-on-specific-time/
источник
Вы можете использовать класс ниже, чтобы запланировать свою задачу каждый день в определенное время
package interfaces; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.Date; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class CronDemo implements Runnable{ public static void main(String[] args) { Long delayTime; ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); final Long initialDelay = LocalDateTime.now().until(LocalDate.now().plusDays(1).atTime(12, 30), ChronoUnit.MINUTES); if (initialDelay > TimeUnit.DAYS.toMinutes(1)) { delayTime = LocalDateTime.now().until(LocalDate.now().atTime(12, 30), ChronoUnit.MINUTES); } else { delayTime = initialDelay; } scheduler.scheduleAtFixedRate(new CronDemo(), delayTime, TimeUnit.DAYS.toMinutes(1), TimeUnit.MINUTES); } @Override public void run() { System.out.println("I am your job executin at:" + new Date()); } }
источник
Date
иTimeUnit
в 2019 годуЧто, если ваш сервер выйдет из строя в 4:59 и вернется в 5:01? Думаю, он просто пропустит пробежку. Я бы порекомендовал постоянный планировщик, такой как Quartz, который где-то хранит свои данные расписания. Затем он увидит, что этот прогон еще не был выполнен, и выполнит его в 5:01.
источник