EJB @ Schedule poczekaj do zakończenia metody
Chcę napisać back-ground job (EJB 3.1), który wykonuje co minutę. W tym celu używam następującej adnotacji:
@Schedule(minute = "*/1", hour = "*")
Który działa dobrze.
Jednak czasami praca może zająć więcej niż jedną minutę. W tym przypadku timer jest nadal uruchamiany, powodując problemy z wątkiem.
Czy jest w jakiś sposób możliwe zakończenie schedulera, jeśli bieżące wykonanie nie jest zakończone?
4 answers
Jeśli tylko jeden timer może być aktywny w tym samym czasie, istnieje kilka rozwiązań.
Po pierwsze @Timer
powinny być prawdopodobnie obecne na @Singleton
. W Singletonie metody są domyślnie zablokowane zapisem, więc kontener zostanie automatycznie zablokowany podczas próby wywołania metody timer, gdy nadal jest w niej aktywność.
W zasadzie wystarczy:
@Singleton
public class TimerBean {
@Schedule(second= "*/5", minute = "*", hour = "*", persistent = false)
public void atSchedule() throws InterruptedException {
System.out.println("Called");
Thread.sleep(10000);
}
}
atSchedule
jest domyślnie zablokowany zapis i zawsze może być w nim aktywny tylko jeden wątek, w tym wywołania inicjowane przez kontener.
Po zablokowaniu, kontener może jednak ponownie spróbować timera, więc aby temu zapobiec, należy użyć blokady odczytu i delegować ją do drugiego bean (druga bean jest potrzebna, ponieważ EJB 3.1 nie pozwala na aktualizację blokady odczytu do blokady zapisu).
The timer 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");
}
}
}
Pracownik:
@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");
}
}
To prawdopodobnie nadal wyświetli hałaśliwy wyjątek w dzienniku, więc bardziej gadatliwym, ale cichszym rozwiązaniem jest użycie explicit boolean:
The timer bean:
@Singleton
public class TimerBean {
@EJB
private WorkerBean workerBean;
@Lock(READ)
@Schedule(second = "*/5", minute = "*", hour = "*", persistent = false)
public void atSchedule() {
workerBean.doTimerWork();
}
}
Pracownik:
@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);
}
}
}
Istnieje kilka innych możliwych wariantów, np. możesz delegować busy check do interceptor, lub wstrzyknąć singleton, który zawiera tylko boolean do timera Bean, i sprawdzić, że boolean tam, itp.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-16 09:28:07
Natknąłem się na ten sam problem, ale rozwiązałem go nieco inaczej.
@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();
}
}
}
Działa to poprzez ustawienie zadania do wykonania w przyszłości (w tym przypadku w ciągu jednej sekundy). Na końcu zadania, planuje zadanie ponownie.
EDIT: zaktualizowano, aby refaktorować "rzeczy" do innej metody, abyśmy mogli chronić wyjątki, aby zmiana harmonogramu zawsze miała miejsce
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-28 10:12:19
Od Java EE 7 możliwe jest użycie " EE-aware "ManagedScheduledExecutorService , czyli w WildFly:
Na przykład @Singleton @Startup @LocalBean
, wprowadź domyślną "managed-scheduled-executor-service" skonfigurowaną w standalone.xml
:
@Resource
private ManagedScheduledExecutorService scheduledExecutorService;
Zaplanuj jakieś zadanie w @PostConstruct
do wykonania tj. co sekundę z stałym opóźnieniem :
scheduledExecutorService.scheduleWithFixedDelay(this::someMethod, 1, 1, TimeUnit.SECONDS);
Tworzy i wykonuje akcję okresową, która zostaje włączona jako pierwsza po danym początkowego opóźnienia, a następnie z danym opóźnieniem między zakończeniem jednej egzekucji a rozpoczęciem następny.[...]
Nie wyłączaj schedulera w tj. @PreDestroy
:
Managed Scheduled Executor Service instances są zarządzane przez serwera aplikacji, dlatego aplikacje Java EE nie mogą wywoływać każda metoda związana z cyklem życia.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-07 10:34:54
Miałem podobny problem. Nie było zadanie, które miało działać co 30 minut i czasami zadanie trwało więcej niż 30 minut, aby zakończyć w tym przypadku inny przypadek pracy zaczynał, podczas gdy poprzedni nie był jeszcze gotowy. Rozwiązałem go, mając statyczną zmienną logiczną, którą moje zadanie ustawiało na true, gdy zaczynało działać, a następnie ustawiało ją z powrotem na false, gdy kończyło. Ponieważ jest zmienną statyczną, wszystkie instancje będą widzieć tę samą kopię przez cały czas. Mógłbyś nawet zsynchronizować blok, gdy u ustawić i wyłączyć zmienną statyczną. class myjob{ private static boolean isRunning = false;
public executeJob(){
if (isRunning)
return;
isRunning=true;
//execute job
isRunning=false;
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-12 17:00:36