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?

Author: Arjan Tijms, 2013-01-18

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.

 61
Author: Arjan Tijms,
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

 7
Author: drone.ah,
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);

ScheduleWithFixedDelay :

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.

 4
Author: Torsten Römer,
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;
  }

}
 0
Author: Rizwan Shaikh,
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