Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Double checked locking pattern with final

I was reading this: https://en.m.wikipedia.org/wiki/Double-checked_locking

And in section Usage in Java, last example:

Semantics of final field in Java 5 can be employed to safely publish the helper object without using volatile:

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;
   }
}

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.[14] Performance of this implementation is not necessarily better than the volatile implementation.

Why tempWrapper is required for correctness? Can't I just remove it and replace with helperWrapper. As far as I understand, the reference to object being created of FinalWrapper<Helper> won't escape to other threads before it's final field value has been initialized in constructor. If some other thread read helperWrapper as not null, then it must have correct value for value.

like image 858
nullptr Avatar asked Feb 24 '26 07:02

nullptr


1 Answers

The getHelper method, if refactored by replacing tempWrapper with helperWrapper, will look like the following:

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

Referring SSA optimization and also this SO answer, the compiler may optimize the above version of getHelper method, with introduction of local vars and reordering the assignments, to look like the following:

public Helper getHelper() {
  // <--- Reordered by compiler here, in the of the scenarios
  FinalWrapper compilerVar4 = helperWrapper;
  FinalWrapper compilerVar1 = helperWrapper;

  if (compilerVar1 == null) {
    synchronized(this) {
      FinalWrapper compilerVar2 = helperWrapper;
      if (compilerVar2 == null) {
        FinalWrapper compilerVar3 = new FinalWrapper<Helper>(new Helper());
        helperWrapper = compilerVar3;
        // <--- Added by compiler
        return compilerVar3.value;
      }
    }
  }

  // <--- Reordered by the compiler (moved to the top)
  // FinalWrapper compilerVar4 = helperWrapper;
  return compilerVar4.value;
}

Now let's say the compilerVar4 reads a value of null, whereas in the next line the compilerVar1 could read a non-null value in the case that another thread has changed the value of helperWrapper by the time this 2nd read occurs. And thus the method will return a null incorrectly (as compilerVar1 == null will return false now).

This issue of returning a null (when the value is in fact a non-null) is avoided by the introduction of the local variable tempWrapper.


As an afterthought, in my understanding the initialization-on-demand holder pattern enables a safe, highly concurrent lazy initialization of static fields with good performance, as recommended in the above Wiki Double-checked locking (Usage in Java) and is referred to in this SO answer.

class Foo {
    private static class HelperHolder {
       public static final Helper helper = new Helper();
    }

    public static Helper getHelper() {
        return HelperHolder.helper;
    }
}
like image 93
Amit Dash Avatar answered Feb 25 '26 21:02

Amit Dash