CDI PostConstruct and volatile fields
Asked Answered
V

4

2

Using a post construct approach when we want to conditionally initialise some of the bean's fields, do we need to care about volatility of the field, since it is a multithread environment?

Say, we have something like this:

@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();
       }
    }
}

So should the fooBean be wrapped into, let's say, the AtomicReference or be a volatile or it would be a redundant extra protection?

P.S. In this particular case it can be reformulated as: are post construct and post destroy events performed by the same thread or not? However I would like to have an answer for a more general case.

Varve answered 9/11, 2018 at 14:26 Comment(0)
A
2

I would say it depends which thread is actually initiating and destroying the contexts. If you use regular events, they are synchronous (asynchronous events have been added in CDI 2.0 with ObservesAsync, see Java EE 8: Sending asynchronous CDI 2.0 events with ManagedExecutorService ) so they are called in the same thread as the caller.

In general, I don't think the same thread is used (in application servers or standalone applications) so I would recommend using volatile to ensure the right value is seen (basically the value constructed seen on destroy thread). However, it is not a use case happening so much to initiate and destroy your application in a concurrent way...

Adynamia answered 9/11, 2018 at 14:53 Comment(0)
B
2

FooService is a singleton which is shared between all managed beans in the application.

Annotation Type ApplicationScoped

private FooBean fooBean is a state of the singleton object.

By default, CDI does not manage concurrency so it is the responsibility of a developer.

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 does not restrict containers to use the same thread for initialization and destruction of the application context. This behavior is implementation specific. In general case those threads will be different because initialization happens on the thread handling the first request to the application but destruction happens on the thread handling request from management console.

Bloodstain answered 9/11, 2018 at 15:21 Comment(2)
do you have by anychange the part of the specification speaking about thread management?Adynamia
This post by Stephan Knitelius summarizes the situation with concurrency management in CDI specification and provides guidance on how to replicate EJB concurrency mechanisms with CDI interceptors.Bloodstain
B
1

You may delegate concurrency management to EJB container - if your runtime environment includes one.

Neither volatile nor AtomicReference are needed in this case!

Following definition will do the job:

@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();
       }
    }
}
Bloodstain answered 9/11, 2018 at 23:37 Comment(0)
M
1

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.

And according to this presentation Bean manager lifecycle: the lifecycle of the bean manager is synchronous between the different states of the process and the sequence is kept: "destroy not before init".

Jboss are the specification lead of CDI 2.0

I do not see any scenario that would require a volatile/protection. Even if T1 inits then T2 destroys, it will be T1 then T2, not T1 and T2 concurrently.

And even if it would be concurrently, to have an issue it would imply weird scenario, edge scenario outside the CDI runtime:

  • T2 calls destroy (fooBean is null and now 'cached' in a register)
  • Then T1 calls init: destroy before init, at this point we are in the 4th dimension of CDI),
  • Then T2 calls destroy (fooBean is already cached in a register so is value is null)).

Or

  • T2 calls a method that access fooBean (fooBean is null and now 'cached' in a register)
  • Then T1 calls init: T1 is initialized whereas fooBean has already been used by T2, at this point we are in the 4th dimension of CDI
  • Then T2 calls destroy (fooBean is already cached in a register so is value is null)).
Maniemanifest answered 12/11, 2018 at 13:0 Comment(4)
thanks for the specification! However for cached value it doesn't need to be accessed to put in L* CPU cache. it doesn't even need to be concurrent calls. volatile provides the guarantees that the CPU caches does contain the latest value written by any thread. In case of concurrency between init and destroy it could happen that you get null value.Adynamia
it could happen that you get null value How and when? Cache is coherent and volatile on x86 is mostly about not caching into register not CPU cacheManiemanifest
First, imagine init and destroy execute at the same time and destroy reaches first the field fooBean, it would see null with or without volatile. Another situation when volatile could help, if one thread executes init while fooBean is cached on all L1 caches, it changes only value in the value in its core while the other cores still hold null in their L1 caches. If destroy executes after without other operation on the cache it could see null without any violation in the Java specification.Adynamia
init and destroy execute at the same time: do we agree that this scenario is not possible? is cached on all L1 caches, it changes only value in the value in its core while the other cores still hold null in their L1 caches.: the cache coherence mechanism will ensure that in other L1 cache, the entries are invalidated. So if destroy executes after, it will not see nullManiemanifest

© 2022 - 2024 — McMap. All rights reserved.