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 调用 destroyfooBean 为空,现在 'cached' 在寄存器中)
  • 然后T1调用init: destroy before init,此时我们处于CDI的第4维),
  • 然后 T2 调用 destroyfooBean 已经缓存在寄存器中,因此值为空)。

  • T2 调用访问 fooBean 的方法(fooBean 为空,现在 'cached' 在寄存器中)
  • 然后T1调用init:T1被初始化,而fooBean已经被T2使用,此时我们在CDI的第4维
  • 然后 T2 调用 destroyfooBean 已经缓存在寄存器中,因此值为空)。