0

I do not understand why local variable is needed here:

public class FinalWrapper<T> {
    public final T value;
    public FinalWrapper(T value) {
        this.value = value;
    }
}

public class Foo {
   private FinalWrapper<Helper> helperWrapper;

   public Helper getHelper() {
      FinalWrapper<Helper> tempWrapper = helperWrapper;

      if (tempWrapper == null) {
          synchronized(this) {
              if (helperWrapper == null) {
                  helperWrapper = new FinalWrapper<Helper>(new Helper());
              }
              tempWrapper = helperWrapper;
          }
      }
      return tempWrapper.value;
   }
}

I get this code from: https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java. What issues can we have if we do not have this local variable? According to the wiki article:

Semantics of final field in Java 5 can be employed to safely publish the helper object without using volatile. The local variable tempWrapper is required for correctness: simply using helperWrapper for both null checks and the return statement could fail due to read reordering allowed under the Java Memory Model. Performance of this implementation is not necessarily better than the volatile implementation.

Thanks in advance.

Oliv
  • 10,221
  • 3
  • 55
  • 76
DPM
  • 1,540
  • 2
  • 18
  • 40
  • You are better off with lazy holder idiom of using a nested private class, which enforces singleton behavior by classloading mechanism. – Ramandeep Nanda Oct 22 '17 at 18:53
  • I have updated my answer below to provide more info about reordering. I hope it exhausts the subject. – cbartosiak Oct 23 '17 at 10:49

1 Answers1

2

To understand the underlying issue let's remove the local variable from the code:

public class Foo {
    private FinalWrapper<Helper> helperWrapper;

    public Helper getHelper() {
        if (helperWrapper == null) {
            synchronized(this) {
                if (helperWrapper == null) {
                    helperWrapper = new FinalWrapper<Helper>(new Helper());
                }
            }
        }
        return helperWrapper.value;
    }
}

We have three reads in this case:

  1. The outer null check.
  2. The inner null check.
  3. The read before the return.

The problem is that due to the read reordering the first read can return a non-null value and the third read can return null. It means that the third read happens before the first one, which is supposed to ensure helperWrapper is initialized...

Adding the local variable solves the issue because we assign helperWrapper value to tempWrapper and then it does not matter in what order tempWrapper is read. If it has a non-null value it is used both for the null check and for the return statement.

It can happen because Java Memory Model allows for such reordering of operations just for the optimization purpose. Look at the quote from here:

What is meant by reordering?

There are a number of cases in which accesses to program variables (object instance fields, class static fields, and array elements) may appear to execute in a different order than was specified by the program. The compiler is free to take liberties with the ordering of instructions in the name of optimization. Processors may execute instructions out of order under certain circumstances. Data may be moved between registers, processor caches, and main memory in different order than specified by the program.

[...]

The compiler, runtime, and hardware are supposed to conspire to create the illusion of as-if-serial semantics, which means that in a single-threaded program, the program should not be able to observe the effects of reorderings. However, reorderings can come into play in incorrectly synchronized multithreaded programs, where one thread is able to observe the effects of other threads, and may be able to detect that variable accesses become visible to other threads in a different order than executed or specified in the program.

[...]

cbartosiak
  • 785
  • 6
  • 11
  • Thanks for your answer, @cbartosiak! Unfortunately, I don't understand how this specific reordering is possible. Lets assume that we analyze the code without local variable (that you've wrote). If we assume that such reordering is possible (the third read to be done before the first one) and if we run this in single threaded program it will throw NullPointerException (because the variable is initialized after the second read). So how is this possible? I trust you, because I've seen a similar example here: http://jeremymanson.blogspot.bg/2008/12/benign-data-races-in-java.html (the last example) – DPM Oct 23 '17 at 11:28
  • But how is that possible when such reordering could change a single threaded execution? Thanks in advance! – DPM Oct 23 '17 at 11:29
  • In a single threaded execution the order does not matter, because the value of helperWrapper cannot be changed between reads. The problem appears in a multi-threaded execution where another thread can write to helperWrapper between the reads. Does it help? – cbartosiak Oct 23 '17 at 11:59
  • To be more precise - in a single threaded execution if the third read returns null, the first one returns null as well. No matter the order is, the value returned is the same. However in a multi threaded execution if the third read returns null, the first one can return non-null or null depending on the fact if another thread writes between these two reads or not. This is called data race. And this is possible due to the reordering. – cbartosiak Oct 23 '17 at 12:08
  • And the last thing - NullPointerException is thrown only when we get a non-null value in the first read, but null in the third. You will not see this happen in a single threaded execution. – cbartosiak Oct 23 '17 at 12:21
  • cbartosiak, thanks for you answers. Unfortunately, I still cannot understand why third read can occur before the first in single threaded execution( I assume we analyze your code). The third read will never be null, because if the first read is null then the variable will be initialized. That is that I cannot understand. I know about compilation reordering and so on, I do not understand why it is legal to permit execution of third read before the first one. – DPM Oct 23 '17 at 18:55
  • In a single-threaded execution the third read actually can be null due to the reordering. This what makes it safe here is that the first read is null as well, so the program executes the synchronized block and writes to helperWrapper. After that there might "another third read" which would be non-null. The reordering is possible, because in most cases helperWrapper is not null and it might a good idea to move the third read "closer" to the first one, even before. – cbartosiak Oct 23 '17 at 19:55
  • And it is legal by design. – cbartosiak Oct 23 '17 at 19:56
  • In other words Java is allowed to do a **speculative** read for the return statement before the first null check happens. The read can be **repeated** if a thread performs a write to helperWrapper. The problem with a multi-threaded execution is that the write is done by another thread, and the third read is not repeated. I don't know how to make it clearer. – cbartosiak Oct 23 '17 at 20:20
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/157342/discussion-between-dpm-and-cbartosiak). – DPM Oct 24 '17 at 06:54