毎分実行されるバックグラウンドジョブ(EJB 3.1)を作成したい。このために、次の注釈を使用します。
@Schedule(minute = "*/1", hour = "*")
正常に動作しています。
ただし、ジョブには1分以上かかる場合があります。この場合、タイマーは引き続き起動され、スレッドの問題が発生します。
現在の実行が完了していない場合、スケジューラを何らかの方法で終了できますか?
同時にアクティブにできるタイマーが1つだけの場合は、いくつかの解決策があります。
まず、@Timer
はおそらく@Singleton
。シングルトンでは、メソッドはデフォルトで書き込みロックされているため、タイマーメソッドがまだ実行されている間にタイマーメソッドを呼び出そうとすると、コンテナは自動的にロックアウトされます。
基本的には次のもので十分です。
@Singleton
public class TimerBean {
@Schedule(second= "*/5", minute = "*", hour = "*", persistent = false)
public void atSchedule() throws InterruptedException {
System.out.println("Called");
Thread.sleep(10000);
}
}
atSchedule
はデフォルトで書き込みロックされており、コンテナによって開始された呼び出しを含め、アクティブにできるスレッドは1つだけです。
ロックアウトされると、コンテナはタイマーを再試行する可能性があるため、これを防ぐには、代わりに読み取りロックを使用して、2番目のBeanに委任します(EJB 3.1では読み取りロックをアップグレードできないため、2番目のBeanが必要です書き込みロック)。
タイマーBean:
@Singleton
public class TimerBean {
@EJB
private WorkerBean workerBean;
@Lock(READ)
@Schedule(second = "*/5", minute = "*", hour = "*", persistent = false)
public void atSchedule() {
try {
workerBean.doTimerWork();
} catch (Exception e) {
System.out.println("Timer still busy");
}
}
}
ワーカーBean:
@Singleton
public class WorkerBean {
@AccessTimeout(0)
public void doTimerWork() throws InterruptedException {
System.out.println("Timer work started");
Thread.sleep(12000);
System.out.println("Timer work done");
}
}
これはおそらくログにノイズの多い例外を出力する可能性が高いため、より冗長でありながら静かな解決策は明示的なブール値を使用することです:
タイマーBean:
@Singleton
public class TimerBean {
@EJB
private WorkerBean workerBean;
@Lock(READ)
@Schedule(second = "*/5", minute = "*", hour = "*", persistent = false)
public void atSchedule() {
workerBean.doTimerWork();
}
}
ワーカーBean:
@Singleton
public class WorkerBean {
private AtomicBoolean busy = new AtomicBoolean(false);
@Lock(READ)
public void doTimerWork() throws InterruptedException {
if (!busy.compareAndSet(false, true)) {
return;
}
try {
System.out.println("Timer work started");
Thread.sleep(12000);
System.out.println("Timer work done");
} finally {
busy.set(false);
}
}
}
他にもいくつかのバリエーションがあります。ビジーチェックをインターセプターに委任するか、ブールBeanのみを含むシングルトンをタイマーBeanに挿入して、そこでブール値をチェックするなどの方法があります。
私は同じ問題にぶつかりましたが、わずかに異なって解決しました。
@Singleton
public class DoStuffTask {
@Resource
private TimerService timerSvc;
@Timeout
public void doStuff(Timer t) {
try {
doActualStuff(t);
} catch (Exception e) {
LOG.warn("Error running task", e);
}
scheduleStuff();
}
private void doActualStuff(Timer t) {
LOG.info("Doing Stuff " + t.getInfo());
}
@PostConstruct
public void initialise() {
scheduleStuff();
}
private void scheduleStuff() {
timerSvc.createSingleActionTimer(1000l, new TimerConfig());
}
public void stop() {
for(Timer timer : timerSvc.getTimers()) {
timer.cancel();
}
}
}
これは、将来(この場合は1秒で)実行するタスクを設定することで機能します。タスクの最後に、タスクを再度スケジュールします。
編集:「もの」を別のメソッドにリファクタリングして、例外をガードできるように更新し、タイマーの再スケジュールが常に発生するようにしました
Java EE 7であるため、「EE対応」 ManagedScheduledExecutorService を使用することができます。つまり、WildFlyでは:
たとえば、@Singleton @Startup @LocalBean
で、standalone.xml
で構成されたデフォルトの「managed-scheduled-executor-service」を挿入します。
@Resource
private ManagedScheduledExecutorService scheduledExecutorService;
fixed delayで毎秒実行される@PostConstruct
のタスクをスケジュールします:
scheduledExecutorService.scheduleWithFixedDelay(this::someMethod, 1, 1, TimeUnit.SECONDS);
指定された初期遅延後に最初に有効になり、その後、1つの実行の終了から次の実行の開始までの間に指定された遅延で有効になる定期的なアクションを作成して実行します。[...]
@PreDestroy
のスケジューラーをシャットダウンしないでください:
Managed Scheduled Executor Serviceインスタンスはアプリケーションサーバーによって管理されるため、Java EEアプリケーションはライフサイクル関連のメソッドを呼び出すことを禁止されています。
よく似たような問題がありました。 30分ごとに実行することになっていたジョブがあり、この場合、ジョブが完了するまでに30分以上かかることがありました。この場合、前のジョブがまだ終了していない間に別のジョブが開始されていました。実行を開始するたびにジョブをtrueに設定し、終了するたびにfalseに戻す静的ブール変数を使用して解決しました。その静的変数なので、すべてのインスタンスは常に同じコピーを参照します。静的変数を設定および設定解除するときにブロックを同期することもできます。 class myjob {private static boolean isRunning = false;
public executeJob(){
if (isRunning)
return;
isRunning=true;
//execute job
isRunning=false;
}
}