매일 오전 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.Calendar
및 java.util.Date
.
- 이 새 API 의 날짜 시간 클래스 즉, LocalDateTime 을 사용하십시오.
- ZonedDateTime 클래스를 사용 하여 일광 절약 문제를 포함한 시간 대별 계산을 처리합니다. 여기에서 튜토리얼과 예제 를 찾을 수 있습니다.
이제 사용 사례를 사용하여 작업을 예약하는 샘플 예제입니다.
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 :
상위 답변에서 내 업그레이드 버전 :
- 유휴 스레드가있는 스레드 풀로 인해 Web Application Server가 중지를 원하지 않는 상황을 수정했습니다.
- 재귀없이
- 사용자 지정 현지 시간으로 작업을 실행합니다. 제 경우에는 벨로루시, 민스크입니다.
/**
* 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);