Why double checking locks to use volatile field?

The origin of the double lock

Single-mode embodiment, the DCL has a (double lock) implementation. In Java programs, and sometimes you may need to postpone some of the high overhead of object initialization operation, and only when these objects start initialization.

Examples of non-coded delay object initialization thread safe below.

/**
 * @author xiaoshu
 */
public class Instance {
}

/**
 * 非线程安全的延迟初始化对象
 *
 * @author xiaoshu
 */
public class UnsafeLazyInitialization {
    private static Instance instance;

    public static Instance getInstance() {
        if (null == instance) {
            instance = new Instance();
        }
        return instance;
    }
}

In UnsafeLazyInitialization class, a thread of execution of code A 1 is assumed at the same time, B 2 thread executes the code. At this point, A thread might see reference to the object instance initialization is not yet complete.

For UnsafeLazyInitialization class, we can getInstance()do the initial synchronization delay processing to achieve thread-safe method. Sample code is as follows.

/**
 * 安全的延迟初始化
 *
 * @author xiaoshu
 */
public class SafeLazyInitialization {
    private static Instance instance;

    public synchronized static Instance getInstance() {
        if (null == instance) {
            instance = new Instance();
        }
        return instance;
    }
}

Because of the getInstance()way to do the synchronization process, synchronized will lead to performance overhead. If the getInstance () method is frequently invoked multiple threads, it will lead to a decline in program execution performance. Conversely, if the getInstance () method will not be invoked frequently by multiple threads, then the delay initialization program will provide satisfactory performance.

Later, he proposed a "smart" Tip: double-checked locking (Double-Checked Locking). I want to reduce synchronization overhead by double-checked locking. The following example code delay is achieved using double-checked locking initialized.

/**
 * 双重检查锁定
 *
 * @author xiaoshu
 */
public class DoubleCheckedLocking {
    private static Instance instance;

    public static Instance getInstance() {
        if (null == instance) {                             //1.第一次检查
            synchronized (DoubleCheckedLocking.class) {     //2.加锁
                if (null == instance) {                     //3:第二次检查
                    instance = new Instance();              //4.问题的根源出在这里
                }
            }
        }
        return instance;
    }
}

Double-checked locking seems perfect, but this is a wrong optimization! In the first thread to execute the code read instance is not null, an object reference instance has not been possible to complete the initialization.

Root of the problem

Double check foregoing fourth example of the locked code (instance = new Instance ();) creates an object. This line of code can be divided into three lines of pseudo-code below.

memory = allocate();    //1.分配对象的内存空间
ctorInstance(memory); //2.初始化对象
instance = memory;        //3.设置instance指向刚分配的内存地址

Between the upper 3 lines of pseudo code 2 and 3, it may be reordered (in some JIT compiler, this reordering occurs is true), then execution timing of reordering between 2 and 3 as follows:

memory = allocate();    //1.分配对象的内存空间
instance = memory;        //3.设置instance指向刚分配的内存地址
                                            //注意,此时对象还没有被初始化!
ctorInstance(memory); //2.初始化对象

Multi-threaded execution timing table

time A thread Thread B
T1 A1: memory allocation object
T2 A3: Set instance points to memory space
T3 B1: determining whether the instance is empty
T4 B2: Because instance is not null, thread B accesses an object instance references
T5 A2: Object Initialization
T6 A4: access to the object instance references

After know the root of the problem happening, we can think of two ways to achieve lazy initialization thread-safe.

1) 2 and 3 allowed reordering

2) allow reordering 2 and 3, but does not allow other threads to "see" this reordering.

Both solutions described later, corresponding to the above two points.

Solution one: based on volatile solution

/**
 * 安全的双重检查锁定
 *
 * @author xiaoshu
 */
public class SafeDoubleCheckedLocking {
    private volatile static Instance instance;

    public static Instance getInstance() {
        if (null == instance) {
            synchronized (SafeDoubleCheckedLocking.class) {
                if (null == instance) {
                    instance = new Instance();//instance为volatile,现在没有问题了。
                }
            }
        }
        return instance;
    }
}

Note: This solution requires version JDK5 or later (since start using the new JSR-133 specification memory model from JDK5, this specification enhances the semantics of volatile).

When an object is declared after a reference volatile, re-ordering between the rows 3 and 2, pseudo-code 3, in a multithreaded environment it will be prohibited.

Solution two: class initialization based solutions

(Ie, before the Class is loaded, and the threads) JVM's class initialization phase, will perform initialization class. During initialization execution class, JVM will go to get a lock. The lock can synchronize multiple threads to initialize the same class.

Based on this feature, you can achieve another thread-safe lazy initialization program (this program is called Initialization On Demand Holder idiom).

/**
 * 基于类初始化的解决方案
 *
 * @author xiaoshu
 */
public class InstanceFactory {
    private static class InstanceHolder {
        private static Instance instance = new Instance();
    }

    public static Instance getInstance() {
        return InstanceHolder.instance; //这里将导致InstanceHolder类被初始化
    }
}

Field initialization delay reduces the cost of creating or initializing a class instance, but increases the cost of access is lazy initialization field. Most of the time, better than normal initialization delay initialization. If you do need a delay to initialize the instance fields using thread-safe, use the description above based on lazy initialization of volatile solution; if you really need a delay to initialize static fields using thread-safe, use the description above class-based initialization scheme.

Guess you like

Origin blog.51cto.com/14230003/2454764