Double-checked locking and Lazy initialization


Lazy initialization is the tactic of delaying the creation of an object, the calculation of a value, or some other expensive process until the first time it is needed. 

This is generally accomplished by maintaining a flag indicating whether the process has taken place. Each time the desired object is summoned, the flag is tested. If it is ready, it is returned. If not, it is initialized on the spot. In multithreaded code, access to the flag must be synchronized to guard against a race condition.

Read more about lazy initialization here...

Let us consider Singleton Pattern again.

Singleton.java


package me.dhanoop.singleton;

/**
 *
 * @author dhanoopbhaskar
 */
public class Singleton {

    private static Helper helper = null;

    public static Helper getHelper() {
        if (helper == null) {
            synchronized (Singleton.class) {
                if (helper == null) {
                    helper = new Helper();
                }
            }
        }
        return helper;
    }

    public static void main(String[] args) {

        Runnable runnable = new Runnable() {

            @Override
            public void run() {
                Singleton.getHelper();
            }
        };

        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
    }
}

Note: This method uses double-checked locking, which should NOT be used prior to JSE 5.0, as it is vulnerable to subtle bugs. The problem is that an out-of-order write may allow the instance reference to be returned before the Singleton constructor is executed.

The above code does NOT work in the presence of either optimizing compilers or shared memory multiprocessors.

The helper field can have a reference before completely creating an object of Helper class.
Thus, a thread which invokes getHelper() could see a non-null reference to a Helper object, but see the default values for fields of the Helper object, rather than the values set in the constructor. This occurs due to compiler based re-orderings (for optimization) or on a multi-processor system, the memory writes may get re-ordered.


Fixing Double-Checked Locking using Volatile

JDK5 and later extends the semantics for volatile so that the system will not allow a write of a volatile to be reordered with respect to any previous read or write, and a read of a volatile cannot be reordered with respect to any following read or write.

With this change, the Double-Checked Locking idiom can be made to work by declaring the helper field to be volatile. This does not work under JDK4 and earlier.


Singleton.java
package me.dhanoop.singleton;

/**
 *
 * @author dhanoopbhaskar
 */
public class Singleton {

    private static volatile Helper helper = null;

    public static Helper getHelper() {
        if (helper == null) {
            synchronized (Singleton.class) {
                if (helper == null) {
                    helper = new Helper();
                }
            }
        }
        return helper;
    }

    public static void main(String[] args) {

        Runnable runnable = new Runnable() {

            @Override
            public void run() {
                Singleton.getHelper();
            }
        };

        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
    }
}

Another solution: 
Here the local variable result, which seems unnecessary, ensures that in cases where helper is already initialized, the volatile field is only accessed once, which can improve the method's overall performance by as much as 25 percent.

package me.dhanoop.singleton;

/**
 *
 * @author dhanoopbhaskar
 */
public class Singleton {

    private static volatile Helper helper = null;

    public static Helper getHelper() {
        Helper result = helper;
        if (result == null) {
            synchronized (Singleton.class) {
                result = helper;
                if (result == null) {
                    helper = result = new Helper();
                }
            }
        }
        return result;
    }

    public static void main(String[] args) {

        Runnable runnable = new Runnable() {

            @Override
            public void run() {
                Singleton.getHelper();
            }
        };

        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
    }
}

Read more about Double-Checked Locking here...

Post a Comment

0 Comments