Ниже приведен мой Singleton EJB, который использует управляемый компонентом Bean:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.PostConstruct;
import javax.ejb.ConcurrencyManagement;
import javax.ejb.ConcurrencyManagementType;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@Startup
@Singleton(mappedName = "MySingletonService")
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
public class MySingletonService {
private final Logger logger = LogManager.getLogger(getClass());
private final AtomicBoolean running = new AtomicBoolean(false);
private final Lock lock = new ReentrantLock();
@PostConstruct
public void initialize() {
try {
initializeInternal();
} catch (Exception cause) {
logger.debug(cause.getMessage(), cause);
}
}
public boolean isRunning() {
logger.debug("Is initialization running?: {}", running.get());
return running.get();
}
public void reInitialize() throws InterruptedException {
initializeInternal();
}
private void initializeInternal() throws InterruptedException {
if (lock.tryLock(5, TimeUnit.MINUTES)) {
try {
setRunning(true);
logger.debug("Initialization running");
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(30));
} catch (Exception cause) {
}
} finally {
setRunning(false);
lock.unlock();
}
}
}
private void setRunning(boolean state) {
running.set(state);
}
}
И мой планировщик следующий:
import java.util.Objects;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.ScheduleExpression;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.ejb.Timeout;
import javax.ejb.Timer;
import javax.ejb.TimerConfig;
import javax.ejb.TimerService;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@Startup
@Singleton(mappedName = "MySchedulerService")
public class MySchedulerService {
private final Logger logger = LogManager.getLogger(getClass());
@Resource
private TimerService timerService;
@EJB(mappedName = "MySingletonService")
private MySingletonService mySingletonService;
@PostConstruct
public void initialize() {
try {
ScheduleExpression schedule = new ScheduleExpression()
.second("*/10")
.minute("*")
.hour("*")
.dayOfMonth("*")
.month("*")
.dayOfWeek("*")
.year("*");
TimerConfig timerConfig = new TimerConfig("MySingletonServiceTimer", false);
timerService.createCalendarTimer(schedule, timerConfig);
} catch (Exception cause) {
logger.error(cause.getMessage(), cause);
}
}
@Timeout
public void scheduledTask(Timer timer) {
logger.debug("Executing scheduler");
if(StringUtils.equals("MySingletonServiceTimer", Objects.toString(timer.getInfo(), null))) {
if(mySingletonService.isRunning()) {
return;
}
try {
mySingletonService.reInitialize();
} catch (Exception cause) {
logger.error(cause.getMessage(), cause);
}
}
}
}
Я хочу, чтобы метод isRunning()
выполнялся, покаinitializeInternal()
выполняет некоторые задачи (в приведенном выше коде он спит).
scheuledTask
выполняется каждые 10 секунд, а initializeInternal()
имеет 30-секундный сон.Я надеялся увидеть сообщение, которое определено в isRunning()
, которое будет зарегистрировано как минимум два раза в течение двух последовательных вызовов initializeInternal()
.Но на самом деле это не так.Когда initializeInternal()
выполняет уточнение, lock
isRunning()
также переходит в режим ожидания.
Согласно Учебное пособие по Java EE - Пример сессионного компонента Singleton
Управляемый компонентом параллелизм
Синглтоны, использующие управляемый компонентом параллелизм, обеспечивают полный параллельный доступ ко всем бизнес-методам и методам тайм-аута в синглтоне.Разработчик синглтона отвечает за синхронизацию состояния синглтона между всеми клиентами.Разработчикам, которые создают синглтоны с управляемым компонентом параллелизмом, разрешается использовать примитивы синхронизации языка программирования Java, такие как синхронизация и volatile, для предотвращения ошибок при одновременном доступе.
Почему это не ведет себя таким образомчто я ожидаю, что это будет;у меня есть какие-то недостатки в моем дизайне?
Изначально для моего MySingletonService
был определен параллельный параллелизм, управляемый контейнером:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.PostConstruct;
import javax.ejb.AccessTimeout;
import javax.ejb.ConcurrencyManagement;
import javax.ejb.ConcurrencyManagementType;
import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@Startup
@Singleton(mappedName = "MySingletonService")
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
public class MySingletonService {
private final Logger logger = LogManager.getLogger(getClass());
private final AtomicBoolean running = new AtomicBoolean(false);
@PostConstruct
public void initialize() {
try {
initializeInternal();
} catch (Exception cause) {
logger.debug(cause.getMessage(), cause);
}
}
@Lock(LockType.READ)
public boolean isRunning() {
logger.debug("Is initialization running?: {}", running.get());
return running.get();
}
@Lock(LockType.WRITE)
@AccessTimeout(value = 5, unit = TimeUnit.MINUTES)
public void reInitialize() {
initializeInternal();
}
private void initializeInternal() {
try {
setRunning(true);
logger.debug("Initialization running");
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(30));
} catch (Exception cause) {
}
} finally {
setRunning(false);
}
}
private void setRunning(boolean state) {
running.set(state);
}
}
Он также вел себя так же, поэтому я подумал, используя BeanУправляемый параллелизм может дать мне то, чего я хочу достичь.
Ниже приведен журнал, который создается для обоих типов управляемого параллелизма, который может объяснить поток выполнения:
2019-02-22 09:58:37,047 : DEBUG : main : MySingletonService : initializeInternal : Initialization running
2019-02-22 09:59:10,037 : DEBUG : EjbTimerPool - 1 : MySchedulerService : scheduledTask : Executing scheduler
2019-02-22 09:59:10,044 : DEBUG : EjbTimerPool - 1 : MySingletonService : isRunning : Is initialization running?: false
2019-02-22 09:59:10,051 : DEBUG : EjbTimerPool - 1 : MySingletonService : initializeInternal : Initialization running
2019-02-22 09:59:40,052 : DEBUG : EjbTimerPool - 2 : MySchedulerService : scheduledTask : Executing scheduler
2019-02-22 09:59:40,053 : DEBUG : EjbTimerPool - 2 : MySingletonService : isRunning : Is initialization running?: false
2019-02-22 09:59:40,053 : DEBUG : EjbTimerPool - 2 : MySingletonService : initializeInternal : Initialization running
2019-02-22 10:00:10,055 : DEBUG : EjbTimerPool - 1 : MySchedulerService : scheduledTask : Executing scheduler
2019-02-22 10:00:10,055 : DEBUG : EjbTimerPool - 1 : MySingletonService : isRunning : Is initialization running?: false
2019-02-22 10:00:10,056 : DEBUG : EjbTimerPool - 1 : MySingletonService : initializeInternal : Initialization running
Примечание:Я использую TomEE 7.0.4 с OpenJDK 8u192.