Singleton with Double-Checked Locking

Lets have a look at the code for basic Lazy Initialization approach:

public class LazzyInitializedSingleton {
 
    private static LazzyInitializedSingleton instance;
 
    private LazzyInitializedSingleton() {
 
    }
 
    // Lazy initialization is done when client first time request for the instance
    public static LazzyInitializedSingleton getInstance() {
        if (instance == null) {    //line 11
            instance = new LazzyInitializedSingleton();   // line12
        }
        return instance;
    }
}

Lazy Initialization implementation will work when the given environment is single threaded. However in multithreaded environment, above code may create more than one instance. For example,

  • Assume that two threads T1 and T2 are calling getInstance() method concurrently.
  • Thread T1 has checked the null condition on line 11 and entered inside if block as no instance has been created yet.
  • Now T1 is inside the if condition, but it has not created instance yet. At the same time another thread T2 reaches to the null condition on line 11. As instance is still null, T2 also enters the if condition.
  • Now both threads will create instance on line 12 which will break the singleton design pattern.
Using Synchronized GetInstance() Method – Naive Approach

So how can we prevent creation of multiple instances in above approach? Easiest way to achieve this is by making getInstance() method thread-safe using synchronized keyword as shown below.

public class ThreadSafeSingleton {
    private static ThreadSafeSingleton instance;
 
    private ThreadSafeSingleton() {
    }
 
    // Thread T2, T3, T4 are waiting for T1 to release the lock
    public static synchronized ThreadSafeSingleton getInstance() {
        // Thread T1 has taken the lock
        if (instance == null) {
            instance = new ThreadSafeSingleton();  // Critical section
        }
        return instance;
    }
}

This will solve our problem. But it leads us to bad performance as only one thread can access getInstance() method at a time even though critical section is only one line (line 11) where instance is being created.

For Example, In above code, as Thread T1 is inside getInstance() method, other threads T2, T3, T4 has to wait until T1 completes its execution of getInstance() method. It reduces performance drastically.

Double Checked Locking

In above approach, we need to bear cost of synchronization all the time we call getInstance() method, but synchronization is only needed on first call when Singleton instance is created.

To improve the performance, lets modify the code of getInstance() method. Instead of declaring entire method synchronized, now we will just include critical section inside synchronized block as shown below:

public static DoubleCheckedLockingSingleton getInstance() 
{
    if (instance == null) 
    {
        synchronized (DoubleCheckedLockingSingleton.class) 
        {
            instance = new DoubleCheckedLockingSingleton();
        }
 
    }
    return instance;
}
Will this improve the performance?

Yes. If instance has already been created, then threads will not enter the if block and directly return instance. They don’t have to wait for lock on synchronized block.

Is it thread-safe? Can you think of any situation which can result into creation of multiple instances?

This will bring us to double checked locking pattern, where we will also add one more null check inside synchronized block as shown below.

package com.codepumpkin.creational.singleton;
 
public class DoubleCheckedLockingSingleton {
 
    // this field should be volatile to avoid half baked object creation
    private static DoubleCheckedLockingSingleton instance;   
 
    private DoubleCheckedLockingSingleton() 
    {
       // 10000 lines of initialization code
    }
 
    public static DoubleCheckedLockingSingleton getInstance() 
    {
        if (instance == null) 
        {
            synchronized (DoubleCheckedLockingSingleton.class) 
            {
                if (instance == null) 
                {
                    instance = new DoubleCheckedLockingSingleton();
                    System.out.println("Instance Created");
                }
            }
 
        }
        return instance;
    }
 
}

Programmers call it double checked locking because there are two checks for (instance == null), one without locking and other with locking i.e. inside synchronized block.

This approach improves performace as threads do not have to wait for lock if object is already created. Also there is no chance of multiple object creation as we are having null check inside synchronized block as well.

Half Baked Object – Issue With Double Checked Locking

Above code looks perfectly fine and it should work correctly, right? Unfortunately, there is no guarantee it will work correctly on all JVM and it may fail in some.

Issue is not with the JVM or our code, but it is with the Java Memory model and the way java code compiles using some of the JIT Compilers. The Java memory model allows what is known as “out-of-order writes” and it is a prime reason for this issue.

What is “out-of-order writes”?

To understand this better, lets look at the process of object creation in Java. Here are the steps which are being followed by most of the compilers.

  1. Allocating a memory to the object
  2. execute the code inside constructor
  3. Assigning memory reference to the field
    Problem is with some of the JIT compilers which reorders step 2 and step 3 in above instructions. To understand how this occurs, consider the following pseudo code for the line:
instance = new DoubleCheckedLockingSingleton();
Pseudo Code
//Allocate memory for Singleton object.
mem = allocate();             
 
//Note that instance is now non-null, but has not been initialized. 
instance = mem;              
 
//Invoke constructor for Singleton passing instance.
invokeDoubleCheckedLockingSingletonConstructor(instance);

So, when object will be created, instance variable will be assigned non-null reference and then rest of the code inside constructor will be executed.

What is Half Baked Object?

If Object was assigned non-null reference and code inside that constructor is still being executed, then that object is not fully constructed or baked and is referred as Half Baked Object.

How Double Checked Locking pattern returns Half Baked Object?

Refer Below code. I have replaced line instance = new DoubleCheckedLockingSingleton(); with pseudo code for better understanding.

public static DoubleCheckedLockingSingleton getInstance() 
{
    if (instance == null)   // Thread T2 
    {
        synchronized (DoubleCheckedLockingSingleton.class) 
        {
            if (instance == null) 
            {
                // pseudo code for instance = new DoubleCheckedLockingSingleton();
                mem = allocate();             
                instance = mem;              
                invokeDoubleCheckedLockingSingletonConstructor(instance);  // line 12
            }
        }
    }
    return instance;
}

Assume that Thread T1 is executing line 12 i.e. it has assigned non-null reference to instance variable, but it has not completed executing code written inside constructor.

At the same time Thread T2 entered getInstance() method and reached at line 3 i.e. outer null-check. As instance is already been assigned, it will not enter the if block and returns half baked object.

Solution to Half Baked Object Issue using volatile

Above problem can be solved by declaring instance field as volatile. However, it will not work in JDK 1.4 and earlier versions.

private static volatile DoubleCheckedLockingSingleton instance;

In JDK 1.5, Some modifications were done in Java Memory Model and behavior of volatile and final fields. From JDK 1.5, The volatile prevents memory writes from being re-ordered, making it impossible for other threads to read uninitialized fields of your singleton through the singleton’s pointer.

In other words, when field is declared as volatile, JVM guarantees completion of all those three steps of object creation (refer pseudo code) before another thread reads the reference from instance variable. This is also known as Happens-Before relationship of volatile field which was introduced in JDK 1.5.

Volatile doesn’t mean what you think, either

A commonly suggested nonfix is to declare the instance field of DoubleCheckedLockingSingleton as volatile. However, while the JMM prevents writes to volatile variables from being reordered with respect to one another and ensures that they are flushed to main memory immediately, it still permits reads and writes of volatile variables to be reordered with respect to nonvolatile reads and writes. That means – unless all instance fields are volatile as well – thread B can still perceive the constructor’s effect as happening after resource is set to reference the newly created Resource.

参考:
《Singleton Design Pattern Using Double Checked Locking》
《Double-Checked Locking with Singleton》
《Double-checked locking: Clever, but broken》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值