EJB @Schedule дождаться завершения метода
Я хочу написать фоновое задание (EJB 3.1), которое выполняется каждую минуту. Для этого я использую следующую аннотацию:
@Schedule(minute = "*/1", hour = "*")
который работает нормально.
Однако иногда работа может занимать более одной минуты. В этом случае таймер все еще срабатывает, вызывая проблемы с потоками.
Возможно ли как-то прекратить работу планировщика, если текущее выполнение не завершено?
4 ответа
Если одновременно активен только 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
по умолчанию блокируется на запись, и в нем может быть только один активный поток, включая вызовы, инициированные контейнером.
После блокировки контейнер может повторить таймер, поэтому для предотвращения этого вы должны использовать вместо этого блокировку чтения и делегировать второй компонент (второй компонент необходим, поскольку EJB 3.1 не позволяет обновить блокировку чтения до блокировка записи).
Бин таймера:
@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");
}
}
}
Рабочий боб:
@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");
}
}
Это, вероятно, все еще выведет шумное исключение в журнале, поэтому более подробное, но более тихое решение - использовать явное логическое значение:
Бин таймера:
@Singleton
public class TimerBean {
@EJB
private WorkerBean workerBean;
@Lock(READ)
@Schedule(second = "*/5", minute = "*", hour = "*", persistent = false)
public void atSchedule() {
workerBean.doTimerWork();
}
}
Рабочий боб:
@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);
}
}
}
Возможны и другие варианты, например, вы можете делегировать проверку занятости перехватчику или добавить одиночный код, содержащий только логическое значение, в компонент таймера, проверить, что логическое значение там и т. Д.
Я столкнулся с той же проблемой, но решил ее немного по-другому.
@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();
}
}
}
Это работает путем настройки задачи для выполнения в будущем (в этом случае, за одну секунду). В конце задачи она снова планирует расписание.
РЕДАКТИРОВАТЬ: Обновлено, чтобы реорганизовать "вещи" в другой метод, чтобы мы могли следить за исключениями, чтобы всегда происходило повторное планирование таймера
Начиная с Java EE 7 можно использовать ManagedScheduledExecutorService с поддержкой EE, то есть в WildFly:
Например, @Singleton @Startup @LocalBean
введите стандартную управляемую службу по умолчанию, настроенную в standalone.xml
:
@Resource
private ManagedScheduledExecutorService scheduledExecutorService;
Запланируйте некоторые задачи в @PostConstruct
выполняется каждую секунду с фиксированной задержкой:
scheduledExecutorService.scheduleWithFixedDelay(this::someMethod, 1, 1, TimeUnit.SECONDS);
Создает и выполняет периодическое действие, которое становится активным первым после заданной начальной задержки, а затем с заданной задержкой между прекращением одного выполнения и началом следующего. [...]
Не выключайте планировщик в т. Е. @PreDestroy
:
Управляемые экземпляры Scheduled Executor Service управляются сервером приложений, поэтому приложениям Java EE запрещено вызывать любой метод, связанный с жизненным циклом.
Ну у меня была похожая проблема. Было задание, которое должно было выполняться каждые 30 минут, и иногда задание занимало более 30 минут, в этом случае запускался другой экземпляр задания, в то время как предыдущий еще не был завершен. Я решил эту проблему, имея статическую логическую переменную, которую мое задание установило бы в true при каждом запуске, а затем вернуло значение false при каждом завершении. Поскольку это статическая переменная, все экземпляры будут постоянно видеть одну и ту же копию. Вы даже можете синхронизировать блок, когда вы устанавливаете и отменяете статическую переменную. class myjob{приватный статический логический isRunning=false;
public executeJob(){
if (isRunning)
return;
isRunning=true;
//execute job
isRunning=false;
}
}