10

Below is a snippet from Effective Java 2nd Edition. The author claims that the following piece of code is 25% faster than a code in which you do not use the result variable. According to the book "What this variable does is to ensure that field is read only once in the common case where it’s already initialized." . I am not able to understand why this code would be faster after the value is initialized compared to if we do not use the local variable result. In either case you will have only one volatile read after initialization whether you use the local variable result or not.

// Double-check idiom for lazy initialization of instance fields 
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;
}
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
veritas
  • 2,444
  • 1
  • 21
  • 30
  • 2
    Uhm, is this the first edition? Double checked locking has been discouraged for some years now – fge Jun 18 '13 at 08:53
  • And the reason is: http://stackoverflow.com/questions/4926681/why-is-double-checked-locking-broken-in-java?rq=1 – Lenymm Jun 18 '13 at 08:55
  • 5
    @fge: Not really. Since Java 5, it's actually an OK pattern: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html – Lukas Eder Jun 18 '13 at 08:56
  • 1
    @LukasEder it is largely discouraged because Java 6+ renders it basically obsolete – fge Jun 18 '13 at 08:59
  • I'm still undecided about these things. Why did [`java.io.File.toPath()`](http://hg.openjdk.java.net/lambda/lambda/jdk/file/tip/src/share/classes/java/io/File.java) use double-checked locking, introduced in Java 1.7? – Lukas Eder Jun 18 '13 at 09:01
  • @LukasEder urgh! I guess it is because they didn't really have a choice here given the many constructors of `File`... They didn't want to make the effort to make `toPath` `final` and initializing it in all of them, I guess – fge Jun 18 '13 at 09:10
  • @fge: I think lazy initialisation is important there, as the call to `FileSystems.getDefault().getPath(path);` is too expensive to be made for every `File` in the Java world... So how to lazy-initialise and do without double-checked locking or synchronisation? – Lukas Eder Jun 18 '13 at 09:22
  • 1
    @Lenymm the link you provided didn't talk about Local Variable Result. – veritas Jun 18 '13 at 09:38

2 Answers2

10

Once field has been initialised, the code is either:

if (field == null) {...}
return field;

or:

result = field;
if (result == null) {...}
return result;

In the first case you read the volatile variable twice whereas in the second you only read it once. Although volatile reads are very fast, they can be a little slower than reading from a local variable (I don't know if it is 25%).

Notes:

  • volatile reads are as cheap as normal reads on recent processors (at least x86)/JVMs, i.e. there is no difference.
  • however the compiler can better optimise a code without volatile so you could get efficiency from better compiled code.
  • 25% of a few nanoseconds is still not much anyway.
  • it is a standard idiom that you can find in many classes of the java.util.concurrent package - see for example this method in ThreadPoolExecutor (there are many of them)
assylias
  • 321,522
  • 82
  • 660
  • 783
  • ok so it means "return field" also constitutes to a volatile read (provided field is volatile) – veritas Jun 18 '13 at 09:59
  • @veritas Yes it does - if you examine the bytecode you will see that it is loaded before being returned. – assylias Jun 18 '13 at 09:59
  • +1: Nice to see an example that is independent of double-checked locking – Lukas Eder Jun 18 '13 at 11:38
  • 1
    An interesting read regarding double check locking (DCL) and safe publication can be found here: http://shipilev.net/blog/2014/safe-public-construction/#_x86_2. Per its testing, there is no performance gain on x86 by using this local variable. And on ARM, using the local variable has a performance gain around 15% over plain DCL. – lcn Nov 01 '15 at 03:08
  • @assylias in his last book, Joshua Bloch says "In particular, the need for the local variable (result) may be unclear. What this variable does is to ensure that field is read only once in the common case where it’s already initialized". It seems to me that the local variable was added mainly to ensure constructed object is readable. I thought partially constructed problem was fixed in JDK 1.5. Why then Joshua Bloch says that local variable ensures that? I am a bit confused, was it added to improve performance or to make sure constructed object is readable? – Eugene Maysyuk Aug 08 '22 at 23:38
  • DCL does not require the local variable. See for example https://stackoverflow.com/q/33427986/829571 – assylias Aug 09 '22 at 01:42
0

Without using a local variable, in most invocations we have effectively

if(field!=null) // true
    return field;

so there are two volatile reads, which is slower than one volatile read.

Actually JVM can merge the two volatile reads into one volatile read and still conform to JMM. But we expect JVM to perform a good faith volatile read every time it's told to, not to be a smartass and try to optimize away any volatile read. Consider this code

volatile boolean ready;

do{}while(!ready); // busy wait

we expect JVM to really load the variable repeatedly.

ZhongYu
  • 19,446
  • 5
  • 33
  • 61