Introduction to the

Double-checked locking (also known as double-checked locking optimization) is a software design pattern.

Its function is to reduce the number of times the lock is acquired in multi-threaded environment, especially in singleton mode.

Software design patterns: Generic solutions to common problems. Templates inherent in programming for some common business.

Lazy initialization: In programming, the deferral of object creation, value calculation, or other expensive procedures until first use.

Singleton pattern: Within a certain range, only one instance object is generated.

In JavaDouble-checked lock

Singleton mode we need to ensure that the instance is initialized only once.

The following example works in a single-threaded environment, where there are thread safety issues (instance being initialized multiple times).

    private static Singleton instance;
    public static Singleton getInstance(a) {
        if (null == instance) {
            instance = new Singleton();
        }
        return instance;
    }
Copy the code

The following examples focus on performance issues. First, locking is expensive, because thread-safety happens when the object is initialized, and global control is done, which is wasteful.

    public synchronized static Singleton getInstance(a) {
        if (null == instance) {
            instance = new Singleton();
        }
        return instance;
    }
Copy the code

In order to control thread safety and ensure performance, double-checked locking mode appears.

    public static Singleton getInstance(a) {
        if (null == instance) {
            synchronized (Singleton.class) {
                if (null == instance) {
                    instance = newSingleton(); }}}return instance;
    }
Copy the code

Logic is as follows

Let’s analyze the execution logic

Suppose there are three threads, T1, T2, and T3, accessing the getInstance method in turn.

  1. T1 checks for Null the first time to enter the synchronization block, T1 holds the lock, and T1 checks for Null the second time to perform object creation.
  2. T2 waits for T1 to release the lock. After the lock is released, T2 enters the second check and returns the instance object without Null.
  3. T3 is not Null on the first check and returns the object directly.

It all seems perfect, but there’s a catch. As we know from the Java memory model, compiler optimizations are reordered.

Instance = new Singleton();

1 Create an initialization object.

2 Reference assignment.

Steps 1 and 2 can be reversed, causing an error in calling object properties before initialization.

private staticSingleton instance; . instance =newSingleton(); .public class Singleton {
    private int age;
    public Singleton(a) {
        this.age = 80; }}Copy the code

This subtle error is not easy to make, but it is there. You can refer to the following report, which records this problem in detail.

www.cs.umd.edu/~pugh/java/…

The report also lists several solutions.

1 using ThreadLocal

    private static final ThreadLocal<Singleton> threadInstance = new ThreadLocal<>();
    public static Singleton getInstance(a) {
        if (null == threadInstance.get()) {
            createInstance();
        }
        return instance;
    }
    private static void createInstance(a) {
        synchronized (Singleton.class) {
            if (instance == null)
                instance = new Singleton();
        }
        threadInstance.set(instance);
    }
Copy the code

2. Using volatile (for reordering)

    private volatile static Singleton instance;
    public static Singleton getInstance(a) {
        if (null == instance) {
            synchronized (Singleton.class) {
                if (null == instance) {
                    instance = newSingleton(); }}}return instance;
    }
Copy the code

The following is a performance comparison report for different scenarios

www.cs.umd.edu/~pugh/java/…

conclusion

This section mainly documents the minor considerations when using the double-checked locking mode.

Welcome to leave a message, learn to share!!