CDI PostConstruct 和可变字段
CDI PostConstruct and volatile fields
当我们想要有条件地初始化一些 bean 的字段时使用 post 构造方法,我们是否需要关心字段的波动性,因为它是一个多线程环境?
说,我们有这样的东西:
@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. 在这种特殊情况下,它可以重新表述为: post 构造和 post 破坏事件由相同的人执行线程与否?但是我想得到一个更一般情况的答案。
我会说这取决于哪个线程实际启动和销毁上下文。
如果使用常规事件,则它们是同步的(异步事件已在 CDI 2.0 中添加 ObservesAsync
,请参阅
Java EE 8: Sending asynchronous CDI 2.0 events with ManagedExecutorService ) 因此它们在与调用者相同的线程中被调用。
一般来说,我不认为使用相同的线程(在应用程序服务器或独立应用程序中)所以我建议使用 volatile
以确保看到正确的值(基本上是在销毁线程)。但是,以并发方式启动和销毁您的应用程序并不是经常发生的用例...
FooService
是应用程序中所有托管 bean 共享的单例。
Annotation Type ApplicationScoped
private FooBean fooBean
是单例对象的状态
默认情况下,CDI 不管理并发性,因此这是开发人员的责任。
In this particular case it can be reformulated as: are post construct and post destroy events performed by the same thread or not?
CDI specification不限制容器使用同一个线程来初始化和销毁应用程序上下文。此行为是特定于实现的。在一般情况下,这些线程会有所不同,因为初始化发生在处理对应用程序的第一个请求的线程上,而销毁发生在处理来自管理控制台的请求的线程上。
您可以将并发管理委托给 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();
}
}
}
According to the specification:
An event with qualifier @Initialized(ApplicationScoped.class) is synchronously fired when the application context is initialized.
An event with qualifier @BeforeDestroyed(ApplicationScoped.class) is synchronously fired when the application context is about to be destroyed, i.e. before the actual destruction.
An event with qualifier @Destroyed(ApplicationScoped.class) is synchronously fired when the application context is destroyed, i.e. after the actual destruction.
并且根据此介绍Bean manager lifecycle:bean 管理器的生命周期在进程的不同状态之间是同步的,并且保持顺序:"destroy not before init"。
Jboss是CDI 2.0
的规格领先
我没有看到任何需要 volatile/protection 的场景。即使T1初始化然后T2销毁,也是T1thenT2,而不是T1和T2同时进行。
即使是并发的,出现问题也意味着奇怪的场景,CDI 运行时之外的边缘场景:
- T2 调用
destroy
(fooBean
为空,现在 'cached' 在寄存器中)
- 然后T1调用
init
: destroy before init,此时我们处于CDI的第4维),
- 然后 T2 调用
destroy
(fooBean
已经缓存在寄存器中,因此值为空)。
或
- T2 调用访问
fooBean
的方法(fooBean
为空,现在 'cached' 在寄存器中)
- 然后T1调用
init
:T1被初始化,而fooBean
已经被T2使用,此时我们在CDI的第4维
- 然后 T2 调用
destroy
(fooBean
已经缓存在寄存器中,因此值为空)。
当我们想要有条件地初始化一些 bean 的字段时使用 post 构造方法,我们是否需要关心字段的波动性,因为它是一个多线程环境?
说,我们有这样的东西:
@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. 在这种特殊情况下,它可以重新表述为: post 构造和 post 破坏事件由相同的人执行线程与否?但是我想得到一个更一般情况的答案。
我会说这取决于哪个线程实际启动和销毁上下文。
如果使用常规事件,则它们是同步的(异步事件已在 CDI 2.0 中添加 ObservesAsync
,请参阅
Java EE 8: Sending asynchronous CDI 2.0 events with ManagedExecutorService ) 因此它们在与调用者相同的线程中被调用。
一般来说,我不认为使用相同的线程(在应用程序服务器或独立应用程序中)所以我建议使用 volatile
以确保看到正确的值(基本上是在销毁线程)。但是,以并发方式启动和销毁您的应用程序并不是经常发生的用例...
FooService
是应用程序中所有托管 bean 共享的单例。
Annotation Type ApplicationScoped
private FooBean fooBean
是单例对象的状态
默认情况下,CDI 不管理并发性,因此这是开发人员的责任。
In this particular case it can be reformulated as: are post construct and post destroy events performed by the same thread or not?
CDI specification不限制容器使用同一个线程来初始化和销毁应用程序上下文。此行为是特定于实现的。在一般情况下,这些线程会有所不同,因为初始化发生在处理对应用程序的第一个请求的线程上,而销毁发生在处理来自管理控制台的请求的线程上。
您可以将并发管理委托给 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();
}
}
}
According to the specification:
An event with qualifier @Initialized(ApplicationScoped.class) is synchronously fired when the application context is initialized.
An event with qualifier @BeforeDestroyed(ApplicationScoped.class) is synchronously fired when the application context is about to be destroyed, i.e. before the actual destruction.
An event with qualifier @Destroyed(ApplicationScoped.class) is synchronously fired when the application context is destroyed, i.e. after the actual destruction.
并且根据此介绍Bean manager lifecycle:bean 管理器的生命周期在进程的不同状态之间是同步的,并且保持顺序:"destroy not before init"。
Jboss是CDI 2.0
的规格领先我没有看到任何需要 volatile/protection 的场景。即使T1初始化然后T2销毁,也是T1thenT2,而不是T1和T2同时进行。
即使是并发的,出现问题也意味着奇怪的场景,CDI 运行时之外的边缘场景:
- T2 调用
destroy
(fooBean
为空,现在 'cached' 在寄存器中) - 然后T1调用
init
: destroy before init,此时我们处于CDI的第4维), - 然后 T2 调用
destroy
(fooBean
已经缓存在寄存器中,因此值为空)。
或
- T2 调用访问
fooBean
的方法(fooBean
为空,现在 'cached' 在寄存器中) - 然后T1调用
init
:T1被初始化,而fooBean
已经被T2使用,此时我们在CDI的第4维 - 然后 T2 调用
destroy
(fooBean
已经缓存在寄存器中,因此值为空)。