J'essaie d'exécuter une certaine tâche tous les jours à 5 heures du matin. J'ai donc décidé d'utiliser ScheduledExecutorService
pour cela, mais jusqu'à présent, j'ai vu des exemples qui montrent comment exécuter des tâches toutes les quelques minutes.
Et je ne trouve aucun exemple qui montre comment exécuter une tâche tous les jours à une heure donnée (5 heures du matin) le matin et en tenant également compte du fait que l'heure d'été est en vigueur -
Ci-dessous mon code qui sera lancé toutes les 15 minutes -
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-t-il un moyen de planifier une tâche tous les jours, 5 heures du matin, en utilisant ScheduledExecutorService
en tenant également compte de l'heure d'été?
Et aussi TimerTask
est mieux pour ceci ou ScheduledExecutorService
?
Comme dans la version actuelle de Java SE 8 avec son excellente API date-heure avec Java.time
, ce type de calcul peut être effectué plus facilement au lieu d'utiliser Java.util.Calendar
et Java.util.Date
.
Voici maintenant un exemple de planification d'une tâche avec votre cas d'utilisation:
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);
La initalDelay
est calculée pour demander au planificateur de retarder l'exécution dans TimeUnit.SECONDS
. Les problèmes de décalage horaire, en millisecondes et moins par unité, semblent négligeables pour ce cas d'utilisation. Mais vous pouvez toujours utiliser duration.toMillis()
et TimeUnit.MILLISECONDS
pour gérer les calculs de planification en millisecondes.
Et aussi TimerTask est mieux pour cela ou ScheduledExecutorService?
NO:ScheduledExecutorService
apparemment meilleur que TimerTask
. StackOverflow a déjà une réponse pour vous .
De @PaddyD,
Vous avez toujours le problème qui vous oblige à le redémarrer deux fois par an si vous voulez qu'il fonctionne à la bonne heure locale. scheduleAtFixedRate ne le couperez pas à moins que vous soyez satisfait du même temps UTC toute l'année.
Comme il est vrai et que @PaddyD a déjà donné une solution de contournement (+1 à lui), je fournis un exemple de travail avec l’API de date/heure Java8 avec ScheduledExecutorService
. Utiliser un fil démon est dangereux
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);
}
}
}
Remarque:
MyTask
est une interface avec la fonction execute
. ScheduledExecutorService
, utilisez toujours awaitTermination
après y avoir invoqué shutdown
: il est toujours probable que votre tâche soit bloquée/dans le blocage et que l’utilisateur attende à jamais.L’exemple précédent que j’avais donné avec Calender était juste une idée que j’avais mentionnée, j’évitais le calcul de l’heure exacte et les problèmes liés à l’heure avancée. Mise à jour de la solution par la plainte de @PaddyD
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);
Si vous n’avez pas le luxe de pouvoir utiliser Java 8, voici ce qui vous convient:
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();
}
}
Il ne nécessite aucune bibliothèque externe et tiendra compte de l'heure avancée. Passez simplement l'heure à laquelle vous souhaitez exécuter la tâche en tant qu'objet Calendar
et la tâche en tant que Runnable
. Par exemple:
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");
N.B. la tâche du minuteur s'exécute dans un thread de démon, ce qui n'est pas recommandé pour l'IO. Si vous devez utiliser un thread utilisateur, vous devrez ajouter une autre méthode qui annule le chronomètre.
Si vous devez utiliser un ScheduledExecutorService
, modifiez simplement la méthode startTimer
comme suit:
private void startTimer()
{
Executors.newSingleThreadExecutor().schedule(new Runnable()
{
Thread.currentThread().setName(runThreadName);
dailyTask.run();
startTimer();
}, getNextRunTime().getTime() - System.currentTimeMillis(),
TimeUnit.MILLISECONDS);
}
Je ne suis pas sûr du comportement, mais vous aurez peut-être besoin d'une méthode stop qui appelle shutdownNow
si vous suivez la route ScheduledExecutorService
, sinon votre application risque de se bloquer lorsque vous essayez de l'arrêter.
Avez-vous envisagé d'utiliser quelque chose comme Quartz Scheduler ? Cette bibliothèque dispose d'un mécanisme permettant de planifier l'exécution quotidienne de tâches à l'aide d'une expression semblable à celle de cron (consultez CronScheduleBuilder
).
Quelques exemples de code (non testé):
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();
}
}
Il y a un peu plus de travail au début et vous devrez peut-être réécrire le code d'exécution de votre travail, mais cela devrait vous donner plus de contrôle sur la façon dont vous voulez que votre travail soit exécuté. En outre, il serait plus facile de modifier le calendrier si vous en aviez besoin.
Java8:
Ma version améliorée de la réponse du haut:
/**
* 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");
}
}
}
J'avais un problème similaire. Je devais planifier un ensemble de tâches à exécuter au cours d'une journée à l'aide de ScheduledExecutorService
. Cela a été résolu par une tâche commençant à 3h30 du matin en planifiant toutes les autres tâches par rapport à son heure actuelle . Et se reprogrammant pour le lendemain à 3h30.
Avec ce scénario, l'heure d'été n'est plus un problème.
Vous pouvez utiliser une simple analyse de date. Si l'heure de la journée est antérieure à maintenant, commençons demain
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);
L'exemple suivant fonctionne pour moi
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);
}
}
référence: https://chynten.wordpress.com/2016/06/03/Java-scheduler-to-run-every-day-on-specific-time/
Pour ajouter la réponse de Victor .
Je recommanderais d’ajouter un chèque pour voir si la variable (dans son cas la longue midnight
) est supérieure à 1440
. Si c'est le cas, j'omettrais la .plusDays(1)
, sinon la tâche ne sera exécutée que le lendemain.
Je l'ai fait simplement comme ceci:
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;
}
Vous pouvez utiliser la classe ci-dessous pour planifier votre tâche chaque jour à une heure précise
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());
}
}