[java] ScheduledExecutorService를 사용하여 매일 특정 시간에 특정 작업을 실행하는 방법은 무엇입니까?

매일 오전 5시에 특정 작업을 실행하려고합니다. 그래서 이것을 사용하기로 결정 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();
    }
}

ScheduledExecutorService일광 절약 시간제 를 고려 하여 매일 오전 5시 작업을 실행하도록 예약 할 수있는 방법이 있습니까?

그리고 TimerTask이것에 더 좋습니다 ScheduledExecutorService.



답변

현재 Java SE 8 릴리스와 마찬가지로 java.time이러한 종류의 계산이 포함 된 우수한 날짜 시간 API 를 사용하면 java.util.Calendarjava.util.Date.

이제 사용 사례를 사용하여 작업을 예약하는 샘플 예제입니다.

ZonedDateTime now = ZonedDateTime.now(ZoneId.of("America/Los_Angeles"));
ZonedDateTime nextRun = now.withHour(5).withMinute(0).withSecond(0);
if(now.compareTo(nextRun) > 0)
    nextRun = nextRun.plusDays(1);

Duration duration = Duration.between(now, nextRun);
long initalDelay = duration.getSeconds();

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(new MyRunnableTask(),
    initalDelay,
    TimeUnit.DAYS.toSeconds(1),
    TimeUnit.SECONDS);

initalDelay의 실행을 지연하기 위해 스케줄러를 물어 계산된다 TimeUnit.SECONDS. 단위 밀리 초 이하의 시차 문제는이 사용 사례에서 무시할 수있는 것으로 보입니다. 그러나 여전히 밀리 초 단위로 스케줄링 계산을 사용 duration.toMillis()하고 TimeUnit.MILLISECONDS처리 할 수 있습니다 .

또한 TimerTask가 이것 또는 ScheduledExecutorService에 더 낫습니까?

NO : ScheduledExecutorService 겉보기보다 나은 TimerTask. StackOverflow는 이미 답을 가지고 있습니다 .

@PaddyD에서

올바른 현지 시간에 실행하려면 1 년에 두 번 다시 시작해야하는 문제가 있습니다. scheduleAtFixedRate는 일년 내내 동일한 UTC 시간에 만족하지 않는 한 그것을 자르지 않을 것입니다.

사실이고 @PaddyD가 이미 해결 방법 (+1)을 제공 했으므로 .NET과 함께 Java8 날짜 시간 API로 작업 예제를 제공하고 ScheduledExecutorService있습니다. 데몬 스레드를 사용하는 것은 위험합니다

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: 작업이 중단 되거나 교착 상태가되고 사용자가 영원히 기다릴 가능성이 항상 있습니다.

Calender로 이전에 사용한 예는 제가 언급 한 아이디어 일 뿐이며 정확한 시간 계산과 일광 절약 문제를 피했습니다. @PaddyD의 불만에 따라 솔루션을 업데이트했습니다.


답변

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


답변

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

NB 타이머 작업은 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따라 내려 가면 호출하는 stop 메서드가 필요할 수 있습니다 ScheduledExecutorService. 그렇지 않으면 중지하려고 할 때 응용 프로그램이 중단 될 수 있습니다.


답변

Quartz Scheduler 와 같은 것을 사용하는 것을 고려해 보셨습니까 ? 이 라이브러리에는 cron like 표현식을 사용하여 매일 정해진 시간에 실행되도록 작업을 예약하는 메커니즘이 있습니다 (참조 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();
    }
}

선행 작업이 조금 더 많고 작업 실행 코드를 다시 작성해야 할 수도 있지만 작업 실행 방법을 더 많이 제어 할 수 있습니다. 또한 필요한 경우 일정을 변경하는 것이 더 쉬울 것입니다.


답변

Java8 :
상위 답변에서 내 업그레이드 버전 :

  1. 유휴 스레드가있는 스레드 풀로 인해 Web Application Server가 중지를 원하지 않는 상황을 수정했습니다.
  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");
        }
    }

}


답변

비슷한 문제가있었습니다. 를 사용하여 하루 동안 실행해야하는 작업을 예약해야했습니다 ScheduledExecutorService. 이것은 그의 현재 시간에 상대적으로 다른 모든 작업을 예약하는 오전 3시 30 분에 시작하는 하나의 작업으로 해결되었습니다 . 그리고 다음날 오전 3시 30 분으로 일정을 변경합니다.

이 시나리오에서는 일광 절약이 더 이상 문제가되지 않습니다.


답변

간단한 날짜 구문 분석을 사용할 수 있습니다. 하루 중 시간이 지금 이전이면 내일 시작하겠습니다.

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