36

Я пытаюсь выполнить определенную задачу каждый день в 5 утра по утрам. Поэтому я решил использовать для этого ScheduledExecutorService, но до сих пор я видел примеры, которые показывают, как запускать задачу каждые несколько минут.Как выполнять определенную задачу каждый день в определенное время с помощью ScheduledExecutorService?

И я не могу найти ни одного примера, который показывает, как выполнить задачу каждый день в определенное время (5 утра) утром, а также принимая во внимание факт перехода на летнее время, а также -

Ниже мой код, который будет работать каждые 15 минут -

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

есть ли способ, я могу запланировать задачу для запуска каждый день 5 часов утра с помощью ScheduledExecutorService учитывая факт летнее время, а?

А также TimerTask лучше для этого или ScheduledExecutorService?

+0

использовать что-то вроде Quartz вместо этого. – millimoose

ответ

53

Как и в случае с нынешней версией java SE 8 с отличным API с датами времени с java.time, такие вычисления можно сделать проще, вместо использования java.util.Calendar и java.util.Date.

  • Использование date time class's i.e. LocalDateTime этого нового API
  • Использование ZonedDateTime класса для обработки во время зоны конкретного расчета, включая вопросы, переход на летнее время. Здесь вы найдете tutorial and example.

Теперь в качестве образца, например, для планирования задания с прецеденту:

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

initalDelay вычисляется задать планировщик, чтобы задержать выполнение в TimeUnit.SECONDS. Вопросы разницы во времени с единицами миллисекунд и ниже, по-видимому, незначительны для этого варианта использования. Но вы можете использовать duration.toMillis() и TimeUnit.MILLISECONDS для обработки вычислений вычислений в миллисекундах.

А также TimerTask лучше для этого или ScheduledExecutorService?

NO:ScheduledExecutorService, казалось бы, лучше, чем TimerTask. StackOverflow has already an answer for you.

От @PaddyD,

У вас еще есть вопрос, в котором вы должны перезапустить это два раза в год , если вы хотите, чтобы работать в нужное местное время. scheduleAtFixedRate не разрешит его, если вы не удовлетворены тем же временем UTC в течение всего года.

Как это верно и @PaddyD уже дали обходной путь (+1 к нему), я обеспечиваю рабочий пример с датой Java8 времени API с ScheduledExecutorService.Using daemon thread is dangerous

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

Примечание:

  • MyTask представляет собой интерфейс с функцией execute.
  • При остановке ScheduledExecutorService, всегда используйте awaitTermination после вызова shutdown на нем: всегда есть вероятность, что ваша задача застряла/зашла в тупик, и пользователь будет ждать вечно.

В предыдущем примере я дал с Календаре был просто идея которой я Упоминание, я избегал точного расчета времени и Daylight Saving вопросы. Обновлено решение по жалобе @PaddyD

+0

Спасибо за предложение, не могли бы вы объяснить мне подробно, как «intDelayInHour» означает, что я буду выполнять свою задачу в 5 часов утра? – AKIWEB

+0

@ TrekkieTechieT-T, пожалуйста, отредактируйте править – Sage

+0

Какова цель aDate? –

5

Считаете ли вы использование чего-то вроде Quartz Scheduler? Эта библиотека имеет механизм планирования задач, которые будут выполняться в заданный период времени каждый день, используя выражение типа cron (см. CronScheduleBuilder).

Некоторые примеры кода (не проверено):

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

Там немного больше работы авансом, и вы, возможно, придется переписать код выполнения задания, но это должно дать вам больше контроля над тем, как вы хотите, вам работу бежать. Также было бы легче изменить расписание, если вам нужно.

1

У меня была аналогичная проблема. Мне пришлось запланировать набор задач, которые должны выполняться в течение дня с использованием ScheduledExecutorService. Это было решено одной задачей, начиная с 3:30, планируя все остальные задачи относительно своего текущего времени. И перепланировался на следующий день в 3:30.

С этим сценарием летнее сбережение больше не является проблемой.

8

В 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); 
+4

Для удобства чтения я бы предложил 'TimeUnit.DAYS.toMinutes (1)' вместо «магического номера» 1440. – philonous

+2

приятный и простой. –

6

Если вы не имеете роскошь быть в состоянии использовать Java 8, следующий будет делать то, что вам нужно:

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

Это не требуют каких-либо внешних библиотек, и будут учитывать переход на летнее время. Просто перейдите в то время, когда вы хотите запустить задачу как объект Calendar, а задача - как Runnable. Например:

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. задача таймера запускается в потоке Daemon, который не рекомендуется выполнять для ввода IO. Если вам нужно использовать пользовательский поток, вам нужно будет добавить еще один метод, который отменяет таймер.

Если вы должны использовать ScheduledExecutorService, просто измените метод startTimer на следующее:

private void startTimer() 
{ 
    Executors.newSingleThreadExecutor().schedule(new Runnable() 
    { 
     Thread.currentThread().setName(runThreadName); 
     dailyTask.run(); 
     startTimer(); 
    }, getNextRunTime().getTime() - System.currentTimeMillis(), 
    TimeUnit.MILLISECONDS); 
} 

Я не уверен в поведении, но вам может понадобиться метод остановки, который вызывает shutdownNow, если вы идете вниз ScheduledExecutorService, в противном случае ваше приложение может зависать, когда вы пытаетесь его остановить.

+0

У меня есть ваше мнение. +1 и спасибо. Однако лучше, если мы не будем использовать поток Daemon (т. Е. Новый Timer (runThreadName, true) '). – Sage

+0

@Sage не беспокоится. Ничья демона прекрасна, если вы не используете IO. Вариант использования, который я написал для этого, был просто простым классом «огонь-и-забыть» для запуска некоторых потоков для выполнения некоторых ежедневных задач домашнего хозяйства. Я полагаю, что если вы выполняете чтение базы данных в потоке задачи таймера, как может указать запрос OP, то вам не следует использовать Daemon и вам понадобится какой-то метод остановки, который вам нужно будет вызвать, чтобы ваше приложение прекратилось. http://stackoverflow.com/questions/7067578/when-are-daemon-threads-useful – PaddyD

+0

@PaddyD Была ли последняя часть i.e использующей ScheduledExecutorSerive правильно ??? Способ создания анонимного класса не выглядит корректным синтаксисом. Кроме того, newSingleThreadExecutor() не имеет правильного метода расписания? –

2

Java8:
Моей upgrage версия от верхнего ответа:

  1. Исправлена ​​ситуация, когда Web Application Server doens't хотят остановить из-за ThreadPool с холостым ходом резьбой
  2. Без рекурсии
  3. Задайте задачу с вашим обычным местным временем, в моем случае это Беларусь, Минск


/** 
* 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"); 
     } 
    } 

} 
0

Вы можете использовать простую дату разбора, если время дня до сих пор, давайте начнем завтра:

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); 
Смежные вопросы