Codziennie o 5 rano staram się wykonać określone zadanie. Postanowiłem więc użyć ScheduledExecutorService
do tego, ale do tej pory widziałem przykłady, które pokazują, jak uruchamiać zadanie co kilka minut.
I nie jestem w stanie znaleźć żadnego przykładu, który pokazuje, jak uruchamiać zadanie codziennie o określonej godzinie (5 rano) rano, a także biorąc pod uwagę fakt czasu letniego -
Poniżej znajduje się mój kod, który będzie uruchamiany co 15 minut -
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();
}
}
Czy jest sposób, aby zaplanować uruchamianie zadania codziennie o 5 rano, ScheduledExecutorService
biorąc pod uwagę również czas letni?
A także TimerTask
jest lepszy do tego lub ScheduledExecutorService
?
Odpowiedzi:
Podobnie jak w przypadku obecnego wydania java SE 8 z doskonałym interfejsem API daty i czasu,
java.time
tego rodzaju obliczenia można wykonać łatwiej, zamiast używaćjava.util.Calendar
ijava.util.Date
.Teraz jako przykładowy przykład planowania zadania w Twoim przypadku użycia:
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
Jest obliczana zadać harmonogramu opóźnić wykonanie wTimeUnit.SECONDS
. Problemy z różnicami czasu z jednostkami milisekund i poniżej wydają się nieistotne w tym przypadku użycia. Ale nadal możesz używaćduration.toMillis()
iTimeUnit.MILLISECONDS
obsługiwać obliczenia planowania w milisekundach.NIE:
ScheduledExecutorService
pozornie lepsze niżTimerTask
. StackOverflow ma już dla Ciebie odpowiedź .Od @PaddyD,
Ponieważ to prawda, a @PaddyD już podał obejście (+1 dla niego), przedstawiam działający przykład z API daty i godziny Java8 z
ScheduledExecutorService
. Używanie wątku demona jest niebezpieczneclass 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); } } }
Uwaga:
MyTask
jest interfejsem z funkcjąexecute
.ScheduledExecutorService
, Zawsze używajawaitTermination
po wywołaniushutdown
: zawsze istnieje prawdopodobieństwo, że Twoje zadanie utknie / zablokuje się, a użytkownik będzie czekał wiecznie.Poprzedni przykład, który podałem z Calenderem, był tylko pomysłem, o którym wspomniałem, unikałem dokładnego obliczania czasu i problemów z czasem letnim. Zaktualizowano rozwiązanie na skargę @PaddyD
źródło
intDelayInHour
sposób będę wykonywał swoje zadanie o 5 rano?scheduleAtFixedRate
nie przerwie, chyba że będziesz zadowolony z tego samego czasu UTC przez cały rok.W Javie 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);
źródło
TimeUnit.DAYS.toMinutes(1)
większej czytelności sugerowałbym zamiast „magicznej liczby” 1440.Jeśli nie masz luksusu korzystania z języka Java 8, wykonaj następujące czynności:
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(); } }
Nie wymaga żadnych zewnętrznych bibliotek i uwzględni czas letni. Po prostu podaj porę dnia, w której chcesz uruchomić zadanie jako
Calendar
obiekt, a zadanie jako plikRunnable
. Na przykład: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");
Uwaga: zadanie czasomierza działa w wątku Daemon, co nie jest zalecane do wykonywania jakichkolwiek operacji we / wy. Jeśli chcesz użyć wątku użytkownika, musisz dodać inną metodę, która anuluje licznik czasu.
Jeśli musisz użyć a
ScheduledExecutorService
, po prostu zmieństartTimer
metodę na następującą:private void startTimer() { Executors.newSingleThreadExecutor().schedule(new Runnable() { Thread.currentThread().setName(runThreadName); dailyTask.run(); startTimer(); }, getNextRunTime().getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS); }
Nie jestem pewien zachowania, ale możesz potrzebować metody zatrzymania, która wywoła,
shutdownNow
jeśli zejdziesz zScheduledExecutorService
trasy, w przeciwnym razie aplikacja może się zawiesić, gdy spróbujesz ją zatrzymać.źródło
new Timer(runThreadName, true)
.).Czy rozważałeś użycie czegoś takiego jak Quartz Scheduler ? Ta biblioteka ma mechanizm planowania zadań do wykonywania codziennie o określonej porze przy użyciu wyrażenia podobnego do crona (spójrz
CronScheduleBuilder
).Przykładowy kod (nie testowany):
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(); } }
Na początku jest trochę więcej pracy i być może będziesz musiał przepisać kod wykonania zadania, ale powinno to dać ci większą kontrolę nad sposobem, w jaki chcesz wykonać zadanie. W razie potrzeby łatwiej byłoby też zmienić harmonogram.
źródło
Java8:
Moja wersja uaktualnienia z najlepszej odpowiedzi:
/** * 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"); } } }
źródło
Miałem podobny problem. Musiałem zaplanować szereg zadań, które powinny być wykonywane w ciągu dnia za pomocą
ScheduledExecutorService
. Zostało to rozwiązane przez jedno zadanie rozpoczynające się o 3:30 rano, planujące wszystkie inne zadania w stosunku do jego aktualnego czasu . I przełożył się na następny dzień o 3:30.W tym scenariuszu czas letni nie jest już problemem.
źródło
Możesz użyć prostej analizy dat, jeśli pora dnia jest wcześniejsza, zacznijmy jutro:
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);
źródło
Żeby podsumować odpowiedź Victora .
Poleciłbym dodać czek, aby zobaczyć, czy zmienna (w jego przypadku długa
midnight
) jest wyższa niż1440
. Jeśli tak, pominęłabym.plusDays(1)
, w przeciwnym razie zadanie zostanie uruchomione dopiero pojutrze.Zrobiłem to po prostu tak:
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; }
źródło
truncatedTo()
Poniższy przykład działa dla mnie
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ódło: https://chynten.wordpress.com/2016/06/03/java-scheduler-to-run-every-day-on-specific-time/
źródło
Możesz użyć poniższej klasy, aby zaplanować swoje zadanie każdego dnia o określonej porze
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()); } }
źródło
Date
iwTimeUnit
2019 rokuCo się stanie, jeśli serwer przestanie działać o 4:59 i wróci o 5:01? Myślę, że po prostu pominie bieg. Poleciłbym stały harmonogram, taki jak Quartz, który przechowywałby gdzieś swoje dane harmonogramu. Wtedy zobaczy, że ten bieg nie został jeszcze wykonany i zrobi to o 5:01.
źródło