java - tiempo - ¿Cómo ejecutar cierta tarea todos los días a una hora determinada utilizando ScheduledExecutorService?
timer.schedule java (9)
Estoy tratando de ejecutar una determinada tarea todos los días a las 5 AM de la mañana. Así que decidí usar ScheduledExecutorService
para esto, pero hasta ahora he visto ejemplos que muestran cómo ejecutar tareas cada pocos minutos.
Y no puedo encontrar ningún ejemplo que muestre cómo ejecutar una tarea todos los días a una hora determinada (5 AM) en la mañana y también teniendo en cuenta el hecho del horario de verano también.
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, puedo programar una tarea para que se ejecute todos los días a las 5 AM de la mañana usando el Servicio de ScheduledExecutorService
considerando el hecho de que el horario de verano también?
¿Y también TimerTask
es mejor para esto o ScheduledExecutorService
?
¿Has considerado usar algo como Quartz Scheduler ? Esta biblioteca tiene un mecanismo para que las tareas de programación se ejecuten en un período de tiempo establecido cada día utilizando una expresión similar a cron (eche un vistazo a 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 un mayor control sobre cómo desea que se ejecute su trabajo. También sería más fácil cambiar el horario si es necesario.
Al igual que con la versión actual de Java SE 8 con su excelente API de fecha y hora con java.time
este tipo de cálculo se puede hacer más fácilmente en lugar de usar java.util.Calendar
y java.util.Date
.
- Use la fecha y hora de la clase, es decir, LocalDateTime de esta nueva API
- Use la clase ZonedDateTime para manejar el cálculo específico de la zona horaria, incluidos los problemas de horario de verano. Encontrarás tutorial y ejemplo aquí.
Ahora como ejemplo de ejemplo para programar una tarea con su caso de uso:
LocalDateTime localNow = LocalDateTime.now();
ZoneId currentZone = ZoneId.of("America/Los_Angeles");
ZonedDateTime zonedNow = ZonedDateTime.of(localNow, currentZone);
ZonedDateTime zonedNext5 ;
zonedNext5 = zonedNow.withHour(5).withMinute(0).withSecond(0);
if(zonedNow.compareTo(zonedNext5) > 0)
zonedNext5 = zonedNext5.plusDays(1);
Duration duration = Duration.between(zonedNow, zonedNext5);
long initalDelay = duration.getSeconds();
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(new MyRunnableTask(), initalDelay,
24*60*60, TimeUnit.SECONDS);
El initalDelay
se calcula para pedir al programador que demore la ejecución en TimeUnit.SECONDS
. Los problemas de diferencia de tiempo con unidades de milisegundos e inferiores parecen ser despreciables para este caso de uso. Pero todavía puede hacer uso de duration.toMillis()
y TimeUnit.MILLISECONDS
para manejar los cálculos de programación en milisegundos.
¿Y también TimerTask es mejor para esto o ScheduledExecutorService?
NO: ScheduledExecutorService
aparentemente mejor que TimerTask
. ya tiene una respuesta para ti .
Desde @PaddyD,
Aún tiene el problema por el cual necesita reiniciarlo dos veces al año si desea que se ejecute a la hora local correcta. scheduleAtFixedRate no lo reducirá a menos que esté satisfecho con la misma hora UTC durante todo el año.
Como es cierto y @PaddyD ya le ha dado una solución (+1 a él), le proporciono un ejemplo de trabajo con Java8 date time API con ScheduledExecutorService
. Usar hilo de daemon es peligroso
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);
}
}
}
Nota:
-
MyTask
es una interfaz con funciónexecute
. - Al detener
ScheduledExecutorService
, siempre useawaitTermination
después de invocar elshutdown
: siempre existe la posibilidad de que su tarea se atasque / bloquee y el usuario espere por siempre.
El ejemplo anterior que di con Calender fue solo una idea que mencioné, evité el cálculo del tiempo exacto y los problemas de horario de verano. Actualizada la solución en la queja de @PaddyD
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/
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);
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);
Si no tiene el lujo de poder usar Java 8, lo siguiente hará lo que necesita:
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 contabilizará el ahorro de luz diurna. Simplemente pase la hora del día en que desea ejecutar la tarea como un objeto de Calendar
y la tarea como un Runnable
. 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");
NB: la tarea del temporizador se ejecuta en un hilo Daemon que no se recomienda para hacer ningún IO. Si necesita usar un hilo de usuario, deberá agregar otro método que cancele el temporizador.
Si tiene que usar un ScheduledExecutorService
, simplemente cambie el método startTimer
a lo 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 a shutdownNow
si baja la ruta de ScheduledExecutorService
, de lo contrario, su aplicación puede bloquearse cuando intenta detenerla.
Solo para sumar la respuesta de Victor .
Recomendaría agregar un cheque para ver si la variable (en su caso, la midnight
prolongada) es más alta que 1440
. Si es así, .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;
}
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ó mediante una tarea que comenzó a las 3:30 AM y programó todas las demás tareas en relación con su tiempo actual . Y reprogramándose para el día siguiente a las 3:30 AM.
Con este escenario, el horario de verano ya no es un problema.
Java8:
Mi versión de actualización de la respuesta superior:
- Se solucionó la situación cuando el servidor de aplicaciones web no quería detenerse, debido a la agrupación de subprocesos con subproceso inactivo
- Sin recursion
- Ejecutar tarea con su hora local personalizada, en mi caso, es Bielorrusia, Minsk
/**
* 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");
}
}
}