15

I'm working my way through item 71, "Use lazy initialization judiciously", of Effective Java (second edition). It suggests the use of the double-check idiom for lazy initialization of instance fields using this code (pg 283):

private volatile FieldType field;
FieldType getField() {
    FieldType result = field;
    if (result == null) {  //First check (no locking)
        synchronized(this) {
            result = field;
            if (result == null)  //Second check (with locking)
                 field = result = computeFieldValue();
        }
     }
     return result;
}

So, I actually have several questions:

  1. Why is the volatile modifier required on field given that initialization takes place in a synchronized block? The book offers this supporting text: "Because there is no locking if the field is already initialized, it is critical that the field be declared volatile". Therefore, is it the case that once the field is initialized, volatile is the only guarantee of multiple thread consistent views on field given the lack of other synchronization? If so, why not synchronize getField() or is it the case that the above code offers better performance?

  2. The text suggests that the not-required local variable, result, is used to "ensure that field is read only once in the common case where it's already initialized", thereby improving performance. If result was removed, how would field be read multiple times in the common case where it was already initialized?

Gray
  • 115,027
  • 24
  • 293
  • 354
Chris Knight
  • 24,333
  • 24
  • 88
  • 134
  • There are a few previous questions on this topic. Please have a look, and if one suitably answers your question(s), consider marking it so. –  Feb 18 '13 at 03:12

3 Answers3

16

Why is the volatile modifier required on field given that initialization takes place in a synchronized block?

The volatile is necessary because of the possible reordering of instructions around the construction of objects. The Java memory model states that the real-time compiler has the option to reorder instructions to move field initialization outside of an object constructor.

This means that thread-1 can initialized the field inside of a synchronized but that thread-2 may see the object not fully initialized. Any non-final fields do not have to be initialized before the object has been assigned to the field. The volatile keyword ensures that field as been fully initialized before it is accessed.

This is an example of the famous "double check locking" bug.

If result was removed, how would field be read multiple times in the common case where it was already initialized?

Anytime you access a volatile field, it causes a memory-barrier to be crossed. This can be expensive compared to accessing a normal field. Copying a volatile field into a local variable is a common pattern if it is to be accessed in any way multiple times in the same method.

See my answer here for more examples of the perils of sharing an object without memory-barriers between threads:

About reference to object before object's constructor is finished

Community
  • 1
  • 1
Gray
  • 115,027
  • 24
  • 293
  • 354
  • It's about constructor operation reordering. I've edited my answer to better explain @pst. The JIT compiler is allowed to reorder constructor operations _after_ it assigns it to `field`. – Gray Feb 17 '13 at 23:21
  • 2
    In other words, as John Wint explains as well in another answer, not having guaranteed visibility means that the reference to your new object might become visible to your thread (i.e. it sees `field` as non-null) before the full construction becomes visible (i.e. `field.member` might read as null even though it is initialized in the constructor). – Medo42 Feb 17 '13 at 23:35
  • 2
    @Medo42 It's about [synchronization order](http://docs.oracle.com/javase/specs/jls/se5.0/html/memory.html#17.4.4), not visibility. This problem would still exist with *immediate visibility* of the assignment. Thus it is the addition of the happens-before *ordering* that changes it from a broken-DCL to a working-DCL. –  Feb 18 '13 at 03:24
  • @pst you are right, this is about ordering, but the two are related (you get guaranteed visibility as a consequence of a happens-before relation). I think my explanation is just a slightly different way of looking at it. Whether the code is still broken with immediate visibility of the assignment depends on how you define "immediate". "At the point in time where the thread happens to actually write it" is useless because that's how it happens anyway. The other option is to define "immediate" in terms of happens-before, and then you end up with exactly what volatile does. – Medo42 Feb 18 '13 at 10:02
7

This a fairly complicated but it is related to now the compiler can rearrange things.
Basically the Double Checked Locking pattern does not work in Java unless the variable is volatile.

This is because, in some cases, the compiler can assign the variable so something other than null then do the initialisation of the variable and reassign it. Another thread would see that the variable is not null and attempt to read it - this can cause all sorts of very special outcomes.

Take a look at this other SO question on the topic.

Community
  • 1
  • 1
Boris the Spider
  • 59,842
  • 6
  • 106
  • 166
3

Good questions.

Why is the volatile modifier required on field given that initialization takes place in a synchronized block?

If you have no synchronization, and you assign to that shared global field there is no promise that all writes that occur on construction of that object will be seen. For instance imagine FieldType looks like.

public class FieldType{
   Object obj = new Object();
   Object obj2 = new Object();
   public Object getObject(){return obj;}
   public Object getObject2(){return obj2;}
}

It is possible getField() returns a non-null instance but that instance getObj() and getObj2() methods can return null values. This is because without synchronization the writes to those fields can race with the consturction of the object.

How is this fixed with volatile? All writes that occur prior to a volatile write are visible after that volatile write occurs.

If result was removed, how would field be read multiple times in the common case where it was already initialized?

Storing locally once and reading throughout the method ensures one thread/process local store and all thread local reads. You can argue premature optimization in those regards but I like this style because you won't run yourself into strange reordering problems that can occur if you don't.

Gray
  • 115,027
  • 24
  • 293
  • 354
John Vint
  • 39,695
  • 7
  • 78
  • 108