The problem of double check lock in Java single case mode __java

Source: Internet
Author: User

The single example creation pattern is a common programming idiom. When used with multithreading, you must use some type of synchronization. When trying to create more efficient code, Java programmers create double check locking idioms that are used in conjunction with the single example creation pattern, limiting the amount of synchronized code. However, due to some of the less common Java memory model details, there is no guarantee that this double check lock idiom works.

It occasionally fails, not always. In addition, the reasons for its failure are not obvious and include some of the hidden details of the Java memory model. These facts will cause the code to fail because double check locking is difficult to track. In the remainder of this article, we will detail the double check locking idiom to understand where it is invalidated.


To understand where dual-check-locking idioms originate, you must understand the common single example creation idiom, as illustrated in Listing 1:


Listing 1. Single Case creation idiom

Import java.util.*;
Class Singleton
{
  private static Singleton instance;
  Private Vector V;
  Private Boolean inUse;

  Private Singleton ()
  {
    v = new Vector ();
    V.addelement (New Object ());
    InUse = true;
  }

  public static Singleton getinstance ()
  {
    if (instance = null)          //1
      instance = new Singleton ();  2 return
    instance;               3
  }
}

The design of this class ensures that only one Singleton object is created. The constructor is declared as the Private,getinstance () method to create only one object. This implementation is suitable for single-threaded routines. However, when multiple threads are introduced, the getinstance () method must be protected by synchronization. If you do not protect the getinstance () method, you may return two different instances of the singleton object. Suppose two threads call the GetInstance () method concurrently and execute the call in the following order: Thread 1 invokes the getinstance () method and decides that instance is null at//1.

Thread 1 enters the if code block but is//2 by thread 2 at the execution line of the code line.

Thread 2 invokes the getinstance () method and decides instance null at//1.

Thread 2 enters the if code block and creates a new Singleton object and assigns the variable instance to the new object at//2.

Thread 2 Returns a Singleton object reference at//3.

Thread 2 is preloaded by thread 1.

Thread 1 starts where it stops and executes the//2 line of code, which causes another Singleton object to be created.

Thread 1 returns this object at//3.

The result is that the getinstance () method creates two Singleton objects, which should have created only one object. This problem can be corrected by synchronizing the getinstance () method to allow only one thread to execute code at the same time, as shown in Listing 2:


Listing 2. Thread-Safe getinstance () method

public static synchronized Singleton getinstance ()
{
  if (instance = = null)          //1 instance
    = new Singleton ( );  2 return
  instance;               3
}

The code in Listing 2 works well for multithreaded access to the GetInstance () method. However, when you analyze this code, you realize that synchronization is required only the first time the method is invoked. Because only the first call executes the code at//2, and only this line of code needs to be synchronized, there is no need to use synchronization for subsequent calls. All other calls are used to determine that the instance is non-null and to return it. Multithreading can execute all calls, except for the first call, securely and concurrently. However, because the method is synchronized, it is necessary to pay the cost of synchronization for each call to the method, even if only the first call requires synchronization.

To make this approach more effective, an idiom called double check locking has emerged. The idea is to avoid the expensive cost of synchronizing all calls except for the first call. The cost of synchronization is different between different JVMs. In the early days, the price was quite high. With the advent of more advanced JVMs, the cost of synchronization is reduced, but access to synchronized methods or blocks still has a performance penalty. Without considering the progress of JVM technology, programmers never want to waste time unnecessarily.

Because only the//2 line in Listing 2 needs to be synchronized, we can just wrap it in a synchronized block, as shown in Listing 3:


Listing 3 getinstance () method

public static Singleton getinstance ()
{
  if (instance = null)
  {
    synchronized (Singleton.class)
      { Instance = new Singleton ();
    }
  }
  return instance;
}

The code in Listing 3 shows the same problem that is illustrated with multithreading and listing 1. When instance is null, two threads can enter the inside of an if statement concurrently. A thread then enters the synchronized block to initialize the instance, while the other thread is blocked. When the first thread exits the synchronized block, the waiting thread enters and creates another Singleton object. Note: When the second thread enters the synchronized block, it does not check that the instance is not NULL.

Double check lock

To deal with the problem in Listing 3, we need to perform a second check on the instance. This is the origin of the "double check lock" name. The result of applying the double check locking idiom to listing 3 is listing 4.


listing 4. Double check Lock Example

public static Singleton getinstance ()
{
  if (instance = null)
  {
    synchronized (singleton.class) {  / /1
      if (instance = null)          //2
        instance = new Singleton ();  3
    }
  } return
  instance;

The theory behind double check locking is that the second check at//2 makes it impossible to create two different Singleton objects (as in Listing 3). Suppose you have the following sequence of events: Thread 1 enters the getinstance () method.

Because instance is null, thread 1 enters the synchronized block at//1.

Thread 1 is preloaded by thread 2.

Thread 2 enters the getinstance () method.

Because instance is still null, thread 2 attempts to acquire a lock at the//1. However, because thread 1 holds the lock, thread 2 blocks at//1.

Thread 2 is preloaded by thread 1.

Thread 1 executes, and thread 1 creates a Singleton object and assigns its reference to instance because the instance is still null at//2.

Thread 1 exits the synchronized block and returns an instance from the GetInstance () method.

Thread 1 is preloaded by thread 2.

Thread 2 Gets the lock at//1 and checks to see if instance is null.

Because the instance is Non-null and no second Singleton object is created, the object created by thread 1 is returned.

Double check lock behind the theory is perfect. Unfortunately, the reality is completely different. the problem with double check locking is that it does not guarantee that it will run smoothly on a single-processor or multiprocessor computer.

the problem of double check locking failure is not attributable to implementation bugs in the JVM, but to the Java platform memory model. The memory model allows so-called "unordered writes," which is one of the main reasons why these idioms fail.

Unordered writes

To explain the problem, the//3 line in Listing 4 above needs to be reviewed. This line of code creates a Singleton object and initializes the variable instance to reference the object. The problem with this line of code is that the variable instance may become Non-null before the Singleton constructor body executes.

What the. This may not be an unexpected statement, but it is true. Before explaining how this phenomenon happened, please accept this fact for a moment, and let's examine how the double check lock is broken. Suppose the code in Listing 4 executes the following sequence of events: Thread 1 enters the getinstance () method.

Because instance is null, thread 1 enters the synchronized block at//1.

Thread 1 advances to//3, but makes the instance non-null until the constructor executes.

Thread 1 is preloaded by thread 2.

Thread 2 checks to see if the instance is null. Because the instance is not NULL, thread 2 Returns the instance reference to a fully constructed, partially initialized singleton object.

Thread 2 is preloaded by thread 1.

Thread 1 completes initializing an object by running the constructor of the Singleton object and returning the reference to it.

This sequence of events occurs when thread 2 returns an object that has not yet performed a constructor.

To demonstrate the occurrence of this event, assume that the code line instance =new Singleton (); The following pseudocode was executed: instance =new Singleton ();

MEM = allocate ();             Allocate memory for Singleton object.
instance = mem;               Note So instance is now Non-null, but
                              //has not been initialized.
Ctorsingleton (instance);      Invoke constructor for Singleton passing
                              //instance.

This pseudocode is not only possible, but is actually happening on some JIT compilers. The order of execution is reversed, but given the current memory model, this is allowed to occur. This line of JIT compilers is only an academic practice for locking the double check.

To illustrate this, assume the code in Listing 5. It contains a split version of the GetInstance () method. I have removed the "double check" to simplify our review of the generated assembly code (listing 6). We only care about how the JIT compiler compiles instance=new Singleton (); Code. In addition, I provide a simple constructor that clearly describes the operation of the constructor in the assembly code.


Listing 5. A single instance class for demonstrating unordered writes

Class Singleton
{
  private static Singleton instance;
  Private Boolean inUse;
  private int val;  

  Private Singleton ()
  {
    inUse = true;
    val = 5;
  }
  public static Singleton getinstance ()
  {
    if (instance = null)
      instance = new Singleton ();
    return instance
  }
}

Listing 6 contains the assembly code generated by the Sun JDK 1.2.1 JIT compiler for the getinstance () method body in Listing 5.


listing 6. Assembly code generated from the code in Listing 5

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.