当我们想有条件地初始化一些bean的字段时,使用后构造方法,因为这是一个多线程环境,我们是否需要关心字段的易变性?
说,我们有这样的东西:
@ApplicationScoped
public class FooService {
private final ConfigurationService configurationService;
private FooBean fooBean;
@Inject
FooService(ConfigurationService configurationService) {
this.configurationService = configurationService;
}
void init(@Observes @Initialized(ApplicationScoped.class) Object ignored) {
if (configurationService.isFooBeanInitialisationEnabled()) {
fooBean = initialiseFooBean(configurationService); // some initialisation
}
}
void cleanup(@Observes @Destroyed(ApplicationScoped.class) Object ignored) {
if (fooBean != null) {
fooBean.cleanup();
}
}
}
那么fooBean
应该被包裹在AtomicReference
中还是成为volatile
呢?否则这将是多余的额外保护吗?
P.S。在这种特殊情况下,可以将其重新表述为:后构造和后破坏事件是否由同一线程执行?但是,我想为一个更一般的情况提供一个答案。
答案 0 :(得分:2)
我会说这取决于哪个线程实际上是在初始化和破坏上下文。
如果您使用常规事件,则它们是同步的(异步事件已在CDI 2.0中使用ObservesAsync
添加,请参见
Java EE 8: Sending asynchronous CDI 2.0 events with ManagedExecutorService),因此它们在与调用方相同的线程中被调用。
通常,我不认为使用同一线程(在应用程序服务器或独立应用程序中),所以我建议使用volatile
以确保看到正确的值(基本上是在destroy线程上看到的构造值) )。但是,用并发方式启动和销毁您的应用程序的情况并不多见。
答案 1 :(得分:2)
FooService
是一个单例,在应用程序中所有托管bean之间共享。
Annotation Type ApplicationScoped
private FooBean fooBean
是单例对象的状态。
默认情况下,CDI不管理并发,因此这是开发人员的责任。
在这种特殊情况下,可以将其重新表示为:后构造和后破坏事件是否由同一线程执行?
CDI specification并不限制容器使用同一线程来初始化和销毁应用程序上下文。此行为是特定于实现的。通常,这些线程会有所不同,因为初始化发生在处理对应用程序的第一个请求的线程上,而销毁发生在来自管理控制台的线程处理的请求上。
答案 2 :(得分:1)
如果运行时环境包括并发管理,则可以将并发管理委派给EJB容器。
在这种情况下,既不需要volatile
也不需要AtomicReference
!
遵循以下定义即可完成工作:
@javax.ejb.Startup // initialize on application start
@javax.ejb.Singleton // EJB Singleton
public class FooService {
private final ConfigurationService configurationService;
private FooBean fooBean;
@javax.inject.Inject
FooService(ConfigurationService configurationService) {
this.configurationService = configurationService;
}
@javax.annotation.PostConstruct
void init() {
if (configurationService.isFooBeanInitialisationEnabled()) {
fooBean = initialiseFooBean(configurationService); // some initialisation
}
}
@javax.annotation.PreDestroy
void cleanup() {
if (fooBean != null) {
fooBean.cleanup();
}
}
}
答案 3 :(得分:1)
According to the specification:
初始化应用程序上下文时,会同时触发带有限定符@Initialized(ApplicationScoped.class)的事件。
当应用程序上下文即将被销毁时,即在实际销毁之前,带有限定符@BeforeDestroyed(ApplicationScoped.class)的事件被同步触发。
在销毁应用程序上下文时(即在实际销毁之后),会同时触发带有限定符@Destroyed(ApplicationScoped.class)的事件。
并且根据此演示文稿Bean manager lifecycle:Bean管理器的生命周期在流程的不同状态之间是同步的,并且保留了以下顺序:“销毁不是在初始化之前”。
Jboss是CDI 2.0的规范负责人
我看不到任何需要波动/保护的情况。即使T1先启动然后T2销毁,也将是T1 then T2,而不是同时发生的T1和T2。
即使是并发的,如果出现问题,也将意味着奇怪的情况,即CDI运行时之外的边缘情况:
destroy
(fooBean
为空,现在已“缓存”在寄存器中)init
:在初始化之前销毁,这时我们处在CDI的第4维上,destroy
(fooBean
已经缓存在寄存器中,因此它为null)。或
fooBean
的方法(fooBean
为null,现在已“缓存”在寄存器中)init
:T1已初始化,而fooBean
已被T2使用,此时我们处于CDI的第4维。destroy
(fooBean
已经缓存在寄存器中,因此它为null)。