Estou tentando executar uma determinada tarefa todos os dias às 5 da manhã. Então decidi usar ScheduledExecutorService
para isso, mas até agora vi exemplos que mostram como executar tarefas a cada poucos minutos.
E não consigo encontrar nenhum exemplo que mostre como executar uma tarefa todos os dias em um determinado horário (5h) da manhã e também considerando o horário de verão -
Abaixo está meu código que será executado a cada 15 minutos -
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();
}
}
Existe alguma maneira de programar uma tarefa para ser executada todos os dias às 5h da manhã, ScheduledExecutorService
considerando o horário de verão também?
E também TimerTask
é melhor para isso ou ScheduledExecutorService
?
Respostas:
Tal como acontece com a versão atual do java SE 8 com sua excelente API de data e hora,
java.time
esse tipo de cálculo pode ser feito mais facilmente em vez de usarjava.util.Calendar
ejava.util.Date
.Agora, como exemplo de amostra para agendar uma tarefa com seu caso de uso:
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);
O
initalDelay
é calculado para pedir ao planejador para atrasar a execução emTimeUnit.SECONDS
. Problemas de diferença de tempo com unidade de milissegundos e abaixo parecem ser insignificantes para este caso de uso. Mas você ainda pode usarduration.toMillis()
eTimeUnit.MILLISECONDS
manipular os cálculos de agendamento em milissegundos.NÃO:
ScheduledExecutorService
aparentemente melhor do queTimerTask
. StackOverflow já tem uma resposta para você .De @PaddyD,
Como é verdade e @PaddyD já deu uma solução alternativa (+1 para ele), estou fornecendo um exemplo funcional com a API de data e hora Java8
ScheduledExecutorService
. Usar o daemon thread é perigosoclass 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); } } }
Nota:
MyTask
é uma interface com funçãoexecute
.ScheduledExecutorService
, use sempreawaitTermination
depois de invocáshutdown
-lo: Sempre há uma probabilidade de sua tarefa travar / travar e o usuário esperar para sempre.O exemplo anterior que dei com o Calender foi apenas uma ideia que mencionei, evitei o cálculo do tempo exato e problemas de horário de verão. Atualizada a solução de acordo com a reclamação de @PaddyD
fonte
intDelayInHour
significa que estarei executando minha tarefa às 5 da manhã?scheduleAtFixedRate
não vai funcionar, a menos que você esteja satisfeito com o mesmo horário UTC o ano todo.Em 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);
fonte
TimeUnit.DAYS.toMinutes(1)
Se você não pode se dar ao luxo de usar o Java 8, o seguinte fará o que você precisa:
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(); } }
Não requer libs externas e contabiliza o horário de verão. Basta informar a hora do dia em que você deseja executar a tarefa como um
Calendar
objeto e a tarefa como umRunnable
. Por exemplo: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, a tarefa do cronômetro é executada em um thread Daemon que não é recomendado para fazer qualquer IO. Se você precisar usar um thread de usuário, precisará adicionar outro método que cancele o cronômetro.
Se você precisar usar um
ScheduledExecutorService
, basta alterar ostartTimer
método para o seguinte:private void startTimer() { Executors.newSingleThreadExecutor().schedule(new Runnable() { Thread.currentThread().setName(runThreadName); dailyTask.run(); startTimer(); }, getNextRunTime().getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS); }
Não tenho certeza do comportamento, mas você pode precisar de um método de parada que chama
shutdownNow
se você seguir aScheduledExecutorService
rota, caso contrário, seu aplicativo pode travar quando você tentar pará-lo.fonte
new Timer(runThreadName, true)
).Você já pensou em usar algo como o Quartz Scheduler ? Esta biblioteca tem um mecanismo para agendar tarefas para rodar em um determinado período de tempo todos os dias usando uma expressão parecida com cron (dê uma olhada em
CronScheduleBuilder
).Algum código de exemplo (não testado):
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(); } }
Há um pouco mais de trabalho inicial e você pode precisar reescrever o código de execução do trabalho, mas deve dar a você mais controle sobre como deseja que o trabalho seja executado. Além disso, seria mais fácil alterar a programação se necessário.
fonte
Java8:
Minha versão de atualização da resposta principal:
/** * 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"); } } }
fonte
Eu tive um problema parecido. Tive que agendar um monte de tarefas que deveriam ser executadas durante um dia de uso
ScheduledExecutorService
. Isso foi resolvido por uma tarefa começando às 3h30 agendando todas as outras tarefas relativamente ao seu horário atual . E remarcando-se para o dia seguinte às 3h30.Com este cenário, o horário de verão não é mais um problema.
fonte
Você pode usar uma análise de data simples, se a hora do dia for anterior a agora, vamos começar amanhã:
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);
fonte
Só para somar a resposta de Victor .
Eu recomendaria adicionar um cheque para ver se a variável (no caso dele, o longo
midnight
) é maior que1440
. Se for, omitirei o.plusDays(1)
, caso contrário, a tarefa só será executada depois de amanhã.Eu fiz simplesmente assim:
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; }
fonte
truncatedTo()
O exemplo a seguir funciona para mim
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); } }
referência: https://chynten.wordpress.com/2016/06/03/java-scheduler-to-run-every-day-on-specific-time/
fonte
Você pode usar a classe abaixo para agendar sua tarefa todos os dias em um horário específico
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()); } }
fonte
Date
eTimeUnit
em 2019E se o seu servidor cair às 4h59 e voltar às 5h01? Eu acho que vai simplesmente pular a corrida. Eu recomendaria um programador persistente como o Quartz, que armazenaria seus dados de programação em algum lugar. Em seguida, verá que essa execução ainda não foi realizada e o fará às 5:01.
fonte
Por que complicar uma situação se você pode apenas escrever assim? (sim -> baixa coesão, codificado -> mas é um exemplo e infelizmente de forma imperativa). Para obter informações adicionais, leia o exemplo de código abaixo;))
package timer.test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.concurrent.*; public class TestKitTimerWithExecuterService { private static final Logger log = LoggerFactory.getLogger(TestKitTimerWithExecuterService.class); private static final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();// equal to => newScheduledThreadPool(1)/ Executor service with one Thread private static ScheduledFuture<?> future; // why? because scheduleAtFixedRate will return you it and you can act how you like ;) public static void main(String args[]){ log.info("main thread start"); Runnable task = () -> log.info("******** Task running ********"); LocalDateTime now = LocalDateTime.now(); LocalDateTime whenToStart = LocalDate.now().atTime(20, 11); // hour, minute Duration duration = Duration.between(now, whenToStart); log.info("WhenToStart : {}, Now : {}, Duration/difference in second : {}",whenToStart, now, duration.getSeconds()); future = executorService.scheduleAtFixedRate(task , duration.getSeconds() // difference in second - when to start a job ,2 // period , TimeUnit.SECONDS); try { TimeUnit.MINUTES.sleep(2); // DanDig imitation of reality cancelExecutor(); // after canceling Executor it will never run your job again } catch (InterruptedException e) { e.printStackTrace(); } log.info("main thread end"); } public static void cancelExecutor(){ future.cancel(true); executorService.shutdown(); log.info("Executor service goes to shut down"); } }
fonte