Comment exécuter certaines tâches tous les jours à un moment donné en utilisant 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 tâche toutes les quelques minutes.

Et je ne suis pas capable de trouver un exemple qui montre comment exécuter une tâche chaque jour, à un moment donné (5h) le matin, et compte tenu également du fait de l'heure d'été ainsi -

ci-dessous est mon code qui s'exécute 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();
    }
}

est-il possible, je peux programmer une tâche pour courir tous les jours 5 heures du matin en utilisant ScheduledExecutorService compte tenu du fait de l'heure d'été ainsi?

et aussi TimerTask est mieux pour cela ou ScheduledExecutorService ?

49
demandé sur AKIWEB 2013-12-05 02:56:46

9 réponses

comme avec la version java SE 8 actuelle avec son excellente API date time avec java.time ce genre de calcul peut être fait plus facilement au lieu d'utiliser java.util.Calendar et java.util.Date .

maintenant comme exemple pour programmer 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);

le initalDelay est calculé pour demander au planificateur de retarder l'exécution dans TimeUnit.SECONDS . Les problèmes de différence de temps avec l'unité en millisecondes et en dessous semblent négligeables pour ce cas d'utilisation. Mais vous pouvez toujours utiliser duration.toMillis() et TimeUnit.MILLISECONDS pour gérer l'ordonnancement computaions en millisecondes.

et aussi TimerTask est mieux pour cela ou ScheduledExecutorService?

: ScheduledExecutorService apparemment mieux que TimerTask . StackOverflow a déjà une réponse pour vous .

De @PaddyD,

vous avez encore le problème par lequel vous devez redémarrer cette deux fois an si vous voulez qu'il fonctionne à la bonne heure locale. scheduleAtFixedRate Je ne le ferai pas à moins que vous soyez heureux avec la même heure UTC toute l'année.

comme c'est vrai et que @PaddyD a déjà donné une solution de contournement(+1 pour lui), je fournis un exemple de travail avec L'API Date time Java8 avec ScheduledExecutorService . L'utilisation de fils de démon est dangereuse

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);
        }
    }
}

Note:

  • MyTask est une interface avec la fonction execute .
  • en arrêtant ScheduledExecutorService , utilisez toujours awaitTermination après avoir invoqué shutdown : il y a toujours une probabilité que votre tâche soit bloquée / bloquée et que l'utilisateur attende à jamais.

l'exemple précédent que J'ai donné avec Calender était juste un idée que j'ai mentionné, j'ai évité calcul de l'heure exacte et questions relatives à l'heure d'été. Mise à jour de la solution sur la plainte de @PaddyD

65
répondu Sage 2017-05-23 12:09:38

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);
16
répondu Victor 2014-10-25 09:47:57

si vous n'avez pas le luxe de pouvoir utiliser Java 8, ce qui suit fera ce dont vous avez besoin:

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 pas de libs externes, et tiendra compte de l'heure d'été. Il suffit de passer dans le temps de la journée vous voulez exécuter la tâche comme un Calendar objet, et la tâche comme un 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 timer tourne dans un thread de démon qui n'est pas recommandé pour faire N'importe quel IO. Si vous besoin pour l'utilisation du fil, vous aurez besoin d'ajouter une autre méthode, ce qui annule la minuterie.

si vous devez utiliser un ScheduledExecutorService , changez simplement la méthode startTimer pour la suivante:

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 pouvez avoir besoin d'une méthode d'arrêt qui appelle shutdownNow si vous suivez la route ScheduledExecutorService , sinon votre application peut être suspendue lorsque vous essayez de l'arrêter.

7
répondu PaddyD 2014-11-10 10:18:30

Avez-vous envisagé d'utiliser quelque chose comme Planificateur Quartz ? Cette bibliothèque a un mécanisme pour programmer des tâches à exécuter à une période de temps définie chaque jour en utilisant une expression de type cron (jetez un oeil à CronScheduleBuilder ).

un code d'exemple (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 ya un peu plus de travail à l'avance, et vous pouvez avoir besoin de réécrire votre code d'exécution des travaux, mais il devrait vous donner plus de contrôle sur la façon dont vous voulez que vous travail à exécuter. En outre, il serait plus facile de changer l'horaire si vous avez besoin.

5
répondu lmika 2018-03-20 10:06:16

Java8:

Ma version de mise à niveau de haut réponse:

  1. situation fixe lorsque le serveur D'Application Web ne veut pas s'arrêter, en raison de threadpool avec fil idle
  2. sans récursion
  3. exécuter tâche avec votre heure locale coutume, 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");
        }
    }

}
4
répondu Alexey Alexeenka 2017-01-05 08:07:07

j'ai eu un problème similaire. J'ai dû programmer un tas de tâches qui devraient être exécutées pendant une journée en utilisant ScheduledExecutorService . Cela a été résolu par une tâche à partir de 3: 30 am programmant toutes les autres tâches relativement à son heure actuelle . Et se reprogrammer pour le lendemain à 3h30.

avec ce scénario, l'heure d'été n'est plus un problème.

2
répondu user987339 2013-12-05 00:06:31

vous pouvez utiliser une simple date parse, si l'Heure de la journée est avant maintenant, nous allons commencer 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
répondu Luc Chevallier 2018-01-08 11:28:02

Juste à ajouter sur de Victor réponse .

je recommande d'ajouter un contrôle pour voir, si la variable (dans son cas le long midnight ) est supérieure à 1440 . Si c'est le cas, j'omettrais le .plusDays(1) , sinon la tâche ne se déroulera qu'après-demain.

je l'ai fait tout 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
répondu Niby 2018-04-30 07:44:52

l'exemple suivant travaille 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);
    }
}

de référence: https://chynten.wordpress.com/2016/06/03/java-scheduler-to-run-every-day-on-specific-time /

1
répondu Shaik Elias 2018-08-10 03:19:20