毎日午前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
を使用する代わりに、これらの種類の計算をより簡単に実行できます。
次に、ユースケースでタスクをスケジュールするためのサンプル例として:
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から、
適切な現地時間で実行したい場合は、これを年に2回再起動する必要があるという問題がまだあります。 scheduleAtFixedRateは、一年中同じUTC時間に満足していない限り、それを削減しません。
それは事実であり、@ PaddyDは既に回避策(彼に+1)を与えているので、ScheduledExecutorService
を使用したJava8日時APIを使用した実例を提供しています。 デーモンスレッドの使用は危険です
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
を使用します。タスクがスタック/デッドロックし、ユーザーが永遠に待機する可能性が常にあります。カレンダーで行った前の例は、ideaだけでしたが、正確な時間の計算と夏時間の問題を回避しました。 @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");
N.B.タイマータスクはデーモンスレッドで実行されますが、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
を呼び出すstopメソッドが必要です。そうしないと、アプリケーションを停止しようとするとアプリケーションがハングする場合があります。
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();
}
}
前もってもう少し作業が必要です。ジョブ実行コードを書き直す必要があるかもしれませんが、ジョブをどのように実行したいかをより詳細に制御できるはずです。また、必要に応じてスケジュールを変更する方が簡単です。
Java8:
トップアンサーからのアップバージョン:
/**
* 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
を使用して、1日に実行する必要のある一連のタスクをスケジュールする必要がありました。これは、午前3時30分に開始する1つのタスクによって、他のすべてのタスクを現在時刻に関連してスケジュールすることで解決されました。翌日の午前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);
次の例は私のために働く
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);
}
}
参照: https://chynten.wordpress.com/2016/06/03/Java-scheduler-to-run-every-day-on-specific-time/
Victor's answer を合計するだけです。
変数(彼の場合、長いmidnight
)が1440
よりも大きいかどうかを確認するチェックを追加することをお勧めします。そうである場合、.plusDays(1)
を省略します。そうでない場合、タスクは明日の翌日にのみ実行されます。
私はこれを次のように単純に行いました:
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;
}
以下のクラスを使用して、毎日特定の時間にタスクをスケジュールできます
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());
}
}