CDI PostConstruct和volatile字段

时间:2018-11-09 14:26:32

标签: java java-ee concurrency cdi volatile

当我们想有条件地初始化一些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。在这种特殊情况下,可以将其重新表述为:后构造和后破坏事件是否由同一线程执行?但是,我想为一个更一般的情况提供一个答案。

4 个答案:

答案 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运行时之外的边缘情况:

  • T2调用destroyfooBean为空,现在已“缓存”在寄存器中)
  • 然后T1调用init:在初始化之前销毁,这时我们处在CDI的第4维上,
  • 然后T2调用destroyfooBean已经缓存在寄存器中,因此它为null)。

  • T2调用一种访问fooBean的方法(fooBean为null,现在已“缓存”在寄存器中)
  • 然后T1调用init:T1已初始化,而fooBean已被T2使用,此时我们处于CDI的第4维。
  • 然后T2调用destroyfooBean已经缓存在寄存器中,因此它为null)。