"Java concurrent programming art" the lazy initialization

In Java, sometimes you need to reduce overhead lazy initialization initialize class and create objects. Everyone is caused by double-checked locking is a common initialization delay technology, but it is a wrong usage.
For example, the following usage:

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

Suppose thread A executed getInstance () object has not completed initialization, the thread B is determined instance variable is null, initialization is also performed again Instance. Then the final two Instance variables will appear.

Of course, we could simply rude to it with the synchronized , ensure the same time only one thread can acquire the lock.

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

Due to the getInstance () method to do the synchronization process, synchrnoized will lead to getInstance () method is frequently invoked multiple threads, will lead to a decline in program execution performance.

In the early, the synchronized there is a huge cost, exclusive of people in order to reduce overhead, take some tips: "double-checked locking" through "double-checked locking" to reduce synchronization overhead. When the following "double-checked locking" code

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

As shown in the above code, if the first inspection site instance null, then there is no need to perform locking and following initialization. It is possible to significantly reduce synchronized overhead. The above code to improve the disadvantages of the previous two examples:

  • Multiple threads trying to create objects between the same thread; by locking to ensure that only one thread can create objects
  • After the object is created, accessed again getInstance () do not need to acquire the lock object has been created good direct return

However, the "double-checked locking" there is a problem, instance = new Instance()this sentence can be split into three lines of pseudo-code as follows:

  • memory = allocate (); // assignment of storage space
  • ctorInstance (memory); // call the object's initialization function
  • instance = memory; // instance will point to the memory address just allocated

Because all three lines of pseudo-code and then the critical region, so the compiler and processor will he do some reordering, it is possible to perform the following sequence:

  • memory = allocate();
  • instance = memory;
  • ctorInstance(memory);

Does not violate the principle of happens-before, so JMM allow reordering, the university is single-threaded code can run, but in a multi-threaded cause problems, that thread B reads an object that has not been fully initialized. The execution order of the flowchart is as follows:

To solve this problem you can start from two aspects:

  1. 2 are disabled and the reordering operation 5
  2. At initialization, does not allow other threads access

The first aspect can be achieved by volatile, that is,

public class NewDoubleCheckLocking{
    private volatile static Instance instance;
    public static Instance getInstance(){
        if(instance == null){
            synchronized(NewDoubleCheckLocking.class){
                instance = new Instance();
            }
        }
        return instance;
    }
}

Recall volatile keyword can ensure visibility of the atoms and a single operation, the process of creating an object can be avoided be reordered.

The second aspect can be resolved through the class initialization

public class InstanceFactory{
    public static class InstanceHolder{
        public static Instance instance = new Instance();
    }
    public static Instance getInstance(){
        return InstanceHolder.instance;
    }
}

A first call is assumed that the getInstance thread, thread B is also the initial call, the following is a schematic view executed.

Mutex semantics and get the same here, although the reordering when the initialization permission, but will not be observed in other threads. Java language specification, for each class or interface C, has a unique corresponding initialization latch LC. LC mapping from C to achieve freedom by the JVM to the specific implementation. JVM will get this initialization locks during class initialization, and each thread gets at least once a lock to make sure that the class has been initialized before.

First stage:
thread A and thread B Class simultaneously to acquire a lock successfully seize thread A and Class set state, and then releases the lock; thus thread B to enter the blocked state diagram below.

第二个阶段:
线程A在释放了锁后就要开始初始化,而线程B获取到了锁,看到Class状态还是initializing,就放弃锁并进入等待状态。

第三个阶段
线程A执行完初始化,获取Class锁,将state设置为initialized,然后唤醒其他等待中的线程。

第四个阶段
线程B被唤醒,线程B尝试获取Class锁,读取到state为initialized,释放锁并访问这个类。

第五个阶段
当初始化完毕后,线程C来访问该Class,线程C获取初始化锁,读取状态,如果为initialized就释放锁,直接访问类

Guess you like

Origin www.cnblogs.com/codeleven/p/10963135.html