以下是我的使用Bean托管并发的Singleton EJB:
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 Tutorial - Singleton Session Bean Example
Bean管理的并发性
使用Bean管理的并发的单例允许完全并发 访问单例中的所有业务和超时方法。的 单身人士的开发商负责确保国家 单例的同步在所有客户端上进行。开发者 使用Bean管理的并发创建单例可以使用 Java编程语言同步原语,例如 同步和易失性,以防止在并发期间出错 访问。
为什么它的行为方式不符合我的预期;我的设计有什么缺陷吗?
最初,我为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一起使用。