Как запускать определенную задачу каждый день в определенное время с помощью ScheduledExecutorService?
Я пытаюсь запускать определенную задачу каждый день в 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
?
Переведено автоматически
Ответ 1
Как и в нынешней версии java SE 8 с ее отличным API даты и времени, с java.time
такие вычисления можно выполнять проще, чем с помощью java.util.Calendar
и java.util.Date
.
- Используйте класс date time, т.е. LocalDateTime этого нового API
- Используйте класс 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 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,
У вас все еще есть проблема, из-за которой вам нужно перезапускать это дважды в год, если вы хотите, чтобы оно запускалось в нужное местное время. scheduleAtFixedRate не сократит ее, если вас не устраивает одно и то же время UTC в течение всего года.
Поскольку это правда, и @PaddyD уже предоставил обходной путь (+ 1 к нему), я привожу рабочий пример с Java8 date time API с ScheduledExecutorService
. Использование daemon thread опасно
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
after invokingshutdown
для нее: всегда есть вероятность, что ваша задача застряла / зашла в тупик, и пользователь будет ждать вечно.
Предыдущий пример, который я привел с Calender, был просто идеей, о которой я упоминал, я избежал проблем с точным расчетом времени и переходом на летнее время. Обновил решение в соответствии с жалобой @PaddyD
Ответ 2
В 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);
Ответ 3
Если вы не можете позволить себе роскошь использовать 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");
Примечание. задача таймера выполняется в потоке демона, который не рекомендуется для выполнения каких-либо операций ввода-вывода. Если вам нужно использовать пользовательский поток, вам нужно будет добавить другой метод, который отменяет таймер.
Если вам нужно использовать ScheduledExecutorService
, просто измените startTimer
метод на следующий:
private void startTimer()
{
Executors.newSingleThreadExecutor().schedule(new Runnable()
{
Thread.currentThread().setName(runThreadName);
dailyTask.run();
startTimer();
}, getNextRunTime().getTime() - System.currentTimeMillis(),
TimeUnit.MILLISECONDS);
}
Я не уверен в поведении, но вам может понадобиться метод stop, который вызывается, shutdownNow
если вы идете по ScheduledExecutorService
маршруту, в противном случае ваше приложение может зависнуть, когда вы попытаетесь его остановить.
Ответ 4
Java8:
Моя обновленная версия из верхнего ответа:
- Исправлена ситуация, когда сервер веб-приложений не хотел останавливаться из-за threadpool с незанятым потоком
- Без рекурсии
- Запустите задачу по своему собственному местному времени, в моем случае это Беларусь, Минск
/**
* 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");
}
}
}