26

Suppose, that I do some initialization in Spring singleton bean @PostConstruct (simplified code):

@Service
class SomeService {
  public Data someData; // not final, not volatile

  public SomeService() { }

  @PostConstruct
  public void init() {
     someData = new Data(....);
  }
}

Should I worry about someData visibility to other beans and mark it volatile?

(suppose that I cannot initialize it in constructor)

And second scenario: what if I overwrite value in @PostConstruct (after for example explicit initialization or initialization in constructor), so write in @PostConstruct will not be first write to this attribute?

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
Piotr Müller
  • 5,323
  • 5
  • 55
  • 82

2 Answers2

30

The Spring framework is not tied into the Java programming language, it is just a framework. Therefore, in general, you need to mark a non-final field that is accessed by different threads to be volatile. At the end of the day, a Spring bean is nothing more than a Java object and all language rules apply.

final fields receive a special treatment in the Java programming language. Alexander Shipilev, The Oracle performance guy, wrote a great article on this matter. In short, when a constructor initializes a final field, the assembly for setting the field value adds an additional memory barrier that assures that the field is seen correctly by any thread.

For a non-final field, no such memory barrier is created. Thus, in general, it is perfectly possible that the @PostConstruct-annotated method initializes the field and this value is not seen by another thread, or even worse, seen when the constructor is yet only partially executed.

Does this mean that you always need to mark non-final fields as volatile?

In short, yes. If a field can be accessed by different threads, you do. Don't make the same mistake that I did when only thinking of the matter for a few seconds (thanks to Jk1 for the correction) and think in terms of your Java code's execution sequence. You might think that your Spring application context is bootstraped in a single thread. This means that the bootstraping thread will not have issues with the non-volatile field. Thus, you might think that everything is in order as long as you do not expose the application context to another thread until it is fully initialized, i.e. the annotated method is called. Thinking like this, you could assume, the other threads do not have a chance to cache the wrong field value as long as you do not alter the field after this bootstrap.

In contrast, the compiled code is allowed to reorder instructions, i.e. even if the @PostConstruct-annotated method is called before the related bean is exposed to another thread in your Java code, this happens-before relationship is not necessarily retained at in the compiled code at runtime. Thus, another thread might always read and cache the non-volatile field while it is either not yet initialized at all or even partially initialized. This can introduce subtle bugs and the Spring documentation does unfortunately not mention this caveat. Such details of the JMM are a reason why I personally prefer final fields and constructor injection.

Update: According to this answer in another question, there are scenarios where not marking the field as volatile would still produce valid results. I investigated this a little further and the Spring framework guarantees as a matter of fact a certain amount of happens-before safety out of the box. Have a look at the JLS on happens-before relationships where it clearly states:

An unlock on a monitor happens-before every subsequent lock on that monitor.

The Spring framework makes use of this. All beans are stored in a single map and Spring acquires a specific monitor each time a bean is registered or retrieved from this map. As a result, the same monitor is unlocked after registering the fully initialized bean and it is locked before retrieving the same bean from another thread. This forces this other thread to respect the happens-before relationship that is reflected by the execution order of your Java code. Thus, if you bootstrap your bean once, all threads that access the fully initialized bean will see this state as long as they access the bean in a canonical manner (i.e. explicit retrieval by querying the application context or auto-wriring). This makes for example setter injection or the use of a @PostConstruct method safe even without declaring a field volatile. As a matter of fact, you should therefore avoid volatile fields as they introduce a run time overhead for each read what can get painful when accessing a field in loops and because the keyword signals a wrong intention. (By the way, by my knowledge, the Akka framework applies a similar strategy where Akka, other than Spring, drops some lines on the problem.)

This guarantee is however only given for the retrieval of the bean after its bootstrap. If you change the non-volatile field after its bootstrap or if you leak the bean reference during its initialization, this guarantee does not longer apply.

Check out this older blog entry which describes this feature in further detail. Apparently, this feature is not documented as even the Spring people are aware of (but did not do anything about in a long time).

Community
  • 1
  • 1
Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
  • The possible problems involve not only caching, but also reordering issues. Just like in the famously flawed double checked locking pattern partly constructed someData object may be exposed if publication is not guaranteed by happens-before relationship. – Jk1 Jun 02 '14 at 10:41
  • "standard Spring application context, no bean should be exposed to more than one thread until after its @PostConstruct", but Spring Web context is very "popular" context, and servlets call for example `@Service` from multiple threads. So in my opinion concurrent access to beans is very common in Spring. – Piotr Müller Jun 02 '14 at 10:49
  • Jep, this is what I just thought which is why I changed my answer. However, the Spring context is normally created in a bootstrap phase in only a single thread before exposing it to multiple threads. Due to the possibility of reordering instructions, this does however not mean that the initialization leaks into another thread. – Rafael Winterhalter Jun 02 '14 at 10:54
  • @raphw So, am I right in that even if `@PostConstruct` write is **the only** explicit write (no initialization in declaration line, no initialization in constructor) then still this only write is **not thread safe**, because only **implicit default** (in this case `null`) initialization has happens-before constraint when no final/volatile specified? – Piotr Müller Jun 02 '14 at 10:57
  • 1
    Yes, you are very right. See my updated answer. If you think about it, it becomes pretty obvious. But it is easy to misinterpret the Java code's execution sequence into assuming it to be save. – Rafael Winterhalter Jun 02 '14 at 11:01
  • Apparently, the answer is *it depends*, as so often. See my once updated answer. – Rafael Winterhalter Jun 02 '14 at 11:24
  • If your application starts multithreading **before** Spring finishes its initialization, **you** do silly things and have no reason to blame Spring. Of course, web applications use many threads, but all those threads (but one ...) are started **after** Spring have completed its initialization. – Serge Ballesta Jun 02 '14 at 11:54
  • All right, Spring actually takes care of the matter. I did some research and updated my answer to reflect this property. – Rafael Winterhalter Jun 02 '14 at 12:04
  • Fine, you research deserves +1. Hope you'll get the bounty. And as you stated than when using Spring *normally* there are no concurrency problem during initialization, I think I can safely delete my own answer. – Serge Ballesta Jun 02 '14 at 12:21
  • 1
    Volatile overhead is negligible in almost all cases, especially when we're talking about bean retrieval, which is relatively rare operation. Premature optimization of that kind may affect stability and portability of the code in future, as we're talking about non-documented behavior here. – Jk1 Jun 02 '14 at 12:31
  • If I only used the features that were documented for example for Hibernate, I would not be able to use much of the framework ;) Volatile overhead is not too bad, that is true. But it sums up if you for example read a field from a loop. Dough Lea does for example always make a local method copy of volatile fields to avoid the additional overhead, if you bother to browse the JCL sources. Its more a general advice: If its not necessary, avoid it. I am mostly concerned that the modifier suggests a change of a field where it is actually "almost immutable". – Rafael Winterhalter Jun 02 '14 at 12:38
7

Should I worry about someData write visibility to other beans and mark it volatile?

I see no reason why you should not. Spring framework provides no additional thread safety guarantees when calling @PostConstruct, so usual visibility issues may still happen. A common approach would be to declare someData final, but if you want to modify the field several times it obviously won't fit.

It should not really matter if it's the first write to the field, or not. According to Java Memory Model reordering/visibility issues apply in both cases. The only exception is made for final fields, which can be written safely on the first time, but later assignments (e.g. via reflection) are not guaranteed to be visible.

volatile, however, can guarantee necessary visibility from the other threads. It also prevents an unwanted exposure of partly-constructed Data object. Due to reordering issues someData reference may be assigned before all neccessary object creation operations are completed, including constructor operations and default value assignments.

Update: According to a comprehensive research made by @raphw Spring stores singleton beans in monitor-guarded map. This is actually true, as we can see from the source code of org.springframework.beans.factory.support.DefaultSingletonBeanRegistry:

public Object getSingleton(String beanName, ObjectFactory singletonFactory) {
    Assert.notNull(beanName, "'beanName' must not be null");
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        ...
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
}

This may provide you with a thread-safety properties on @PostConstruct, but I would not consider it as sufficient guarantee for a number of reasons:

  1. It affect only singleton-scoped beans, providing no guarantees for the beans of other scopes: request, session, global session, accidentally exposed prototype scope, custom user scopes (yes, you can create one by yourself).

  2. It ensures write to someData is protected, but it gives no guarantees to the reader thread. One can construct an equivalent, but simplified example here, where data write is monitor-guarder and reader thread is not affected by any happens-before relationship here and can read outdated data:

    public class Entity {
    
        public Object data;
    
        public synchronized void setData(Object data) {
           this.data = data;
        }
    }
    
  3. The last, but not least: this internal monitor we're talking about is an implementation detail. Being undocumented it is not guaranteed to stay forever and may be changed without further notice.

Side note: All stated above is true for beans, that are subject of multithreaded access. For prototype-scoped beans it is not really the case, unless they are exposed to several threads explicitly, e.g. by injection into a singleton-scoped bean.

Jk1
  • 11,233
  • 9
  • 54
  • 64
  • Upvoted but can you give some source of `Spring framework provides no additional thread safety guarantees when calling @PostConstruct`? Also what if `@PostConstruct` has the only and first write to attribute? (excluding internal java initialization of attributes) – Piotr Müller Jun 02 '14 at 10:10
  • I'll try to find a credible source to prove the first statement. As for your second question, please refer to updated answer. – Jk1 Jun 02 '14 at 10:21
  • 1
    @killer_PL The common rule to find out whether something is thread-safe or not: "unless you were explicitly told that something is thread-safe, consider it not thread-safe". – Alexey Malev Jun 02 '14 at 10:40
  • @Jk1 So, to clarify: in example code from question, when calling this bean after Spring initialization, from other thread we can still see `someDate == null` as true, right? – Piotr Müller Jun 02 '14 at 10:59
  • 2
    One more point I'd like to mention: even if real implementation contains code that provides visibility guarantees, like `synchronized` block, unless javadoc and/or other documentation clearly states that visibility guarantees is a part of contract, you should not rely on implementation because it can be changed without declaring a war. – Alexey Malev Jun 02 '14 at 11:01
  • 1
    That's right. It may be not possible to spot on x86 hardware due to some implementation details, but Sparc or PowerPC can allow such a reordering. – Jk1 Jun 02 '14 at 11:02
  • 1
    Looks like Spring makes use of the JMM to avoid this reordering issue. I updated my answer on this matter. – Rafael Winterhalter Jun 02 '14 at 12:05
  • 1
    @raphw, while you're correct regarding the implementation details, it's still not a part of specification and may be changed anytime without notice – Jk1 Jun 02 '14 at 12:27
  • I do not think that this would be feasible as they explicitly state that constructor and setter injection are feasible. From the JIRA issue, I assume that they want to make it part of the contract but they did not yet have the time to spell it out. – Rafael Winterhalter Jun 02 '14 at 12:29
  • @raphw, it shouldn't take long to put several phrases into documentation. However, this issue is unresolved for 6(!) years. So I personally would not rely on these plans and threaten codebase's stability by avoiding reasonable precautions. – Jk1 Jun 02 '14 at 12:37
  • @Jk1: Then again, the feature is implemented even longer than the issue. I asked them for an official source for this behavior. Let's see what we get out of it. – Rafael Winterhalter Jun 02 '14 at 12:39