Thursday, February 25, 2010

The dreaded double check pattern in java

The problem with double check locking in java is well documented. Yet even a seasoned programmer can get overzealous trying to optimize synchronization of code that creates singletons and fall prey to the trap. Consider the code
public class Sample {
  private static Sample s = null ;
  public static Sample getSample() {
    if (s == null) {
      s = new Sample() ;
    }
  return s ;
  }
}
Listing 1
This code is not thread safe. If 2 threads t1 and t2 enter the getSample() method at the same time, they are likely to get different instances of sample. This can be fixed easily by adding the synchronized keyword to the getSample() method.
public class Sample {
  private static Sample s = null ;
  public static synchronized Sample getSample() {
    if (s == null) {
      s = new Sample() ;
    }
    return s ;
  }
}
Listing 2
Now the getSample method works correctly. Before entering the getSample method, thread t1 accquires a lock. Any other thread t2 that needs to enter the method will block until t1 exits the method and releases the lock. Code works. Life is good. This is where the smart programmer if not careful, can outsmart himself. He will notice that in reality only the first call to getSample, which creates the instance, needs to be synchronized and subsequent calls that merely return s are paying an unnecessary penalty. He decides to optimize the code to
public class Sample {
  private static Sample s = null ;
  public static Sample getSample() {
    if (s == null) {
      synchronized(Sample.class) {
        s = new Sample() ;
      }
    }
    return s ;
  }
}
Listing 3
Our java guru quickly realizes that this code has the same problem that listing 1 has. So he fine tunes it further.
public class Sample {
  private static Sample s = null ;
  public static Sample getSample() {
    if (s == null) {
      synchronized(Sample.class) {
        if (s == null) {
          s = new Sample() ;
        }
      }
    }
    return s ;
  }
}
Listing 4
By adding an additional check withing the synchronized block, he has ensured that only one thread will ever create an instance of the sample. This is the double check pattern. Our guru's friend, a java expert, buddy reviews the code. Code is checked in and product is shipped. Life is good right ?

Wrong !! Let us say thread t1 enters getSample. s is null. It gets a lock. Within the synchronized block, it checks that s is still null and then executes the constructor for Sample. Before the execution of the constructor completes t1 is swapped out and t2 gets control. Since the constructor did not complete, s is partially initialized. It is not null, but has some corrupt or incomplete value. When t2 enters getSample, it sees that s is not null and returns a corrupt value.

In summary, the double check pattern does not work. The options are to synchronize at a method level as in s listing 2 or to forego lazy initialization. Another option that works is to use a nested holder class that delays initialization until the get method is called.
public class Sample {
  private static class SampleHolder {
    public static Sample INSTANCE = new Sample() ;
  }
  public static Sample getSample()  {
    return SampleHolder.INSTANCE ;
  }
}

Listing 5

3 comments:

  1. Hi Manoj,
    What you have written is right, however the explanation is a bit incomplete.

    The reason why "s i not NULL after going through the constructor" has to do with the liberal Java Memory Model; The JMM does not say that an object has to be fully initialized before it is treated as not equal to null. So a compiler writer can choose to interpret it as per his wish. That is where the problem is.

    If the JVM could guarantee that a such thing will happen, then DCL will work for sure..

    2) This is the same reason that the inner class SampleHolder works; According to JVM spec, inner static classes are fully initialized before they are used; Hence you are guaranteed that when they are not null, they have been constructed in entirety.

    Thanks,
    Radha.

    ReplyDelete
  2. Hi Radha

    JMM is one of the reasons why the problem might occur. But not the only reason.

    The reason SampleHolder works is due to the simple reason that static members are initialized before the class is used.

    I would also make INSTANCE final.

    ReplyDelete
  3. Hi Manoj,

    There could be another solution if Singleton is written in JAVA by using Enum way as explained in book "Effective Java".

    public enum Singleton {
    INSTANCE;
    }

    Thanks
    Javin
    Why String is immutable in Java

    ReplyDelete