EJB @Schedule дождитесь завершения метода

Я хочу написать фоновое задание (EJB 3.1), которое выполняется каждую минуту. Для этого я использую следующую аннотацию:

@Schedule(minute = "*/1", hour = "*")

который работает нормально.

Однако иногда задание может занимать более одной минуты. В этом случае таймер все еще запускается, вызывая проблемы с потоками.

Как это возможно, прекратить планировщик, если текущее выполнение не завершено?

Ответ 1

Если одновременно может быть активен только один таймер, есть несколько решений.

Прежде всего @Timer должен присутствовать на @Singleton. В методах 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 по умолчанию заблокирован по записи, и в нем может быть только один поток, включая вызовы, инициированные контейнером.

После блокировки контейнер может повторить попытку таймера, поэтому, чтобы предотвратить это, вы вместо этого используете блокировку чтения и делегируете второй bean (требуется второй bean, потому что EJB 3.1 не разрешить обновление блокировки чтения блокировки записи).

Таймер 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, и проверять, что boolean существует и т.д.

Ответ 2

Я столкнулся с той же проблемой, но решил ее немного по-другому.

@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();
        }
    }

}

Это работает, настраивая задачу для выполнения в будущем (в данном случае за одну секунду). В конце задачи он снова планирует задачу.

EDIT: Обновлено для рефакторинга "материала" в другой метод, чтобы мы могли защищать исключения, чтобы всегда происходило перепланирование таймера.

Ответ 3

Так как Java EE 7 можно использовать "EE-aware" ManagedScheduledExecutorService, то есть в WildFly:

В примере a @Singleton @Startup @LocalBean введите стандартную "управляемую-запланированную-исполнитель-услугу", настроенную в standalone.xml:

@Resource
private ManagedScheduledExecutorService scheduledExecutorService;

Запланируйте выполнение какой-либо задачи в @PostConstruct, т.е. каждую секунду с фиксированной задержкой:

scheduledExecutorService.scheduleWithFixedDelay(this::someMethod, 1, 1, TimeUnit.SECONDS);

scheduleWithFixedDelay:

Создает и выполняет периодическое действие, которое сначала включается после заданной начальной задержки, а затем с данной задержкой между прекращением одного исполнения и началом следующий. [...]

Не завершать планировщик, т.е. @PreDestroy:

Управляемые экземпляры Службы экстренных служб управляются сервер приложений, поэтому приложениям Java EE запрещается ссылаться любой связанный с жизненным циклом метод.

Ответ 4

Хорошо, у меня была аналогичная проблема. Было задание, которое должно было запускаться каждые 30 минут, и иногда работа занимала более 30 минут, чтобы завершить в этом случае другой экземпляр задания начинался, пока предыдущий еще не был завершен. Я решил это, имея статическую логическую переменную, которую моя задача будет устанавливать на true, когда она запустится, а затем вернет ее значение false до тех пор, пока она не закончится. Поскольку его статическая переменная, все экземпляры будут видеть одну и ту же копию во все времена. Вы даже можете синхронизировать блок, когда u установлен и отключить статическую переменную.   class myjob {   private static boolean isRunning = false;

public executeJob(){
if (isRunning)
    return;
isRunning=true;
//execute job
isRunning=false;
  }

}