Estoy tratando de ejecutar una determinada tarea todos los días a las 5 de la mañana. Así que decidí usarlo ScheduledExecutorService
para esto, pero hasta ahora he visto ejemplos que muestran cómo ejecutar la tarea cada pocos minutos.
Y no puedo encontrar ningún ejemplo que muestre cómo ejecutar una tarea todos los días a una hora en particular (5 a.m.) de la mañana y también considerando el hecho del horario de verano:
A continuación se muestra mi código que se ejecutará 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();
}
}
¿Hay alguna manera de que pueda programar una tarea para que se ejecute todos los días a las 5 a.m. de la mañana usando ScheduledExecutorService
teniendo en cuenta el hecho del horario de verano también?
¿Y también TimerTask
es mejor para esto o ScheduledExecutorService
?
Respuestas:
Al igual que con la versión actual de Java SE 8 con su excelente API de fecha y hora,
java.time
este tipo de cálculo se puede hacer más fácilmente en lugar de usarjava.util.Calendar
yjava.util.Date
.Ahora, como ejemplo de muestra para programar una tarea con su 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);
Se
initalDelay
calcula para pedirle al planificador que retrase la ejecución enTimeUnit.SECONDS
. Los problemas de diferencia de tiempo con milisegundos de unidad e inferiores parecen ser insignificantes para este caso de uso. Pero aún puede utilizarduration.toMillis()
yTimeUnit.MILLISECONDS
manejar los cálculos de programación en milisegundos.NO:
ScheduledExecutorService
aparentemente mejor queTimerTask
. StackOverflow ya tiene una respuesta para ti .De @PaddyD,
Como es cierto y @PaddyD ya ha dado una solución (+1 para él), estoy proporcionando un ejemplo de trabajo con la API de fecha y hora de Java8 con
ScheduledExecutorService
. Usar hilo de demonio es peligrosoclass 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
es una interfaz con funciónexecute
.ScheduledExecutorService
, usar siempreawaitTermination
después de invocarloshutdown
: siempre existe la posibilidad de que su tarea se bloquee o se bloquee y el usuario esperaría para siempre.El ejemplo anterior que di con Calender fue solo una idea que mencioné, evité el cálculo de la hora exacta y los problemas de horario de verano. Se actualizó la solución según la queja de @PaddyD
fuente
intDelayInHour
significa que ejecutaré mi tarea a las 5 a.m. de la mañana?scheduleAtFixedRate
no lo cortará a menos que esté satisfecho con la misma hora UTC durante todo el año.En 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);
fuente
TimeUnit.DAYS.toMinutes(1)
lugar del "número mágico" 1440.Si no tiene el lujo de poder utilizar Java 8, lo siguiente hará lo que necesite:
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(); } }
No requiere ninguna biblioteca externa y tendrá en cuenta el horario de verano. Simplemente ingrese la hora del día en la que desea ejecutar la tarea como un
Calendar
objeto y la tarea como unRunnable
. Por ejemplo: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");
Nota: la tarea del temporizador se ejecuta en un subproceso Daemon que no se recomienda para realizar ninguna E / S. Si necesita utilizar un hilo de usuario, deberá agregar otro método que cancele el temporizador.
Si tiene que usar un
ScheduledExecutorService
, simplemente cambie elstartTimer
método por el siguiente:private void startTimer() { Executors.newSingleThreadExecutor().schedule(new Runnable() { Thread.currentThread().setName(runThreadName); dailyTask.run(); startTimer(); }, getNextRunTime().getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS); }
No estoy seguro del comportamiento, pero es posible que necesite un método de detención que llame
shutdownNow
si sigue laScheduledExecutorService
ruta; de lo contrario, su aplicación puede bloquearse cuando intente detenerla.fuente
new Timer(runThreadName, true)
).¿Ha considerado utilizar algo como Quartz Scheduler ? Esta biblioteca tiene un mecanismo para programar tareas para que se ejecuten en un período de tiempo establecido todos los días usando una expresión tipo cron (échale un vistazo
CronScheduleBuilder
).Algún código de ejemplo (no probado):
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(); } }
Hay un poco más de trabajo por adelantado y es posible que deba volver a escribir el código de ejecución de su trabajo, pero debería darle más control sobre cómo desea que se ejecute su trabajo. Además, sería más fácil cambiar el horario si fuera necesario.
fuente
Java8:
mi versión de actualización de la respuesta superior:
/** * 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"); } } }
fuente
Tuve un problema similar. Tuve que programar un montón de tareas que deberían ejecutarse durante un día usando
ScheduledExecutorService
. Esto se resolvió con una tarea que comenzaba a las 3:30 a.m. y programaba todas las demás tareas en relación con su hora actual . Y reprogramarse para el día siguiente a las 3:30 AM.Con este escenario, el horario de verano ya no es un problema.
fuente
Puede usar un análisis de fecha simple, si la hora del día es anterior a ahora, comencemos mañana:
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);
fuente
Solo para sumar la respuesta de Víctor .
Recomendaría agregar un cheque para ver si la variable (en su caso la larga
midnight
) es mayor que1440
. Si es así, omitiría el.plusDays(1)
; de lo contrario, la tarea solo se ejecutará pasado mañana.Lo hice simplemente así:
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; }
fuente
truncatedTo()
El siguiente ejemplo funciona para mí
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); } }
referencia: https://chynten.wordpress.com/2016/06/03/java-scheduler-to-run-every-day-on-specific-time/
fuente
Puede usar la clase a continuación para programar su tarea todos los días a una hora determinada
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()); } }
fuente
Date
yTimeUnit
en 2019¿Qué pasa si su servidor deja de funcionar a las 4:59 a. M. Y vuelve a las 5:01 a. M.? Creo que simplemente se saltará la carrera. Recomendaría un programador persistente como Quartz, que almacenaría sus datos de programación en algún lugar. Luego verá que esta ejecución aún no se ha realizado y lo hará a las 5:01 AM.
fuente