web-dev-qa-db-fra.com

Comment exécuter certaines tâches chaque jour à une heure particulière à l'aide de ScheduledExecutorService?

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?

54
AKIWEB

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
  • Lors de l’arrêt de 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 

74
Sage

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);
18
Victor

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.

7
PaddyD

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.

5
lmika

Java8:
Ma version améliorée de la réponse du haut: 

  1. Situation fixe lorsque Web Application Server ne veut pas s'arrêter, en raison d'un pool de threads avec un thread inactif
  2. Sans récursion
  3. Exécuter la tâche avec votre heure locale personnalisée, dans mon cas, c'est la Biélorussie, 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");
        }
    }

}
5
Alexey Alexeenka

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.

2
user987339

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);
1
Luc Chevallier

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/

1
Shaik Elias

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;
}
1
Niby

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());
    }
}
0
Amol Suryawanshi