JAVA design pattern-singleton pattern (Singleton)

Singleton

I have always felt that it is useless to learn rote memorization. Let us learn one of the design patterns from a natural perspective-the singleton pattern.

The so-called singleton mode, as its name suggests, can only have one instance globally. We all know that we usually create instances through the constructor new, so we must make the constructor private, and the variables private and static (to prevent being modified and accessed through.) if we want to ensure that the singleton is private.

Ⅰ Lazy man-thread is not safe

In the following implementation, the private static variable uniqueInstance will not be instantiated when the class is loaded. The advantage of this is that if the class is not used, then the uniqueInstance will not be instantiated, thus saving resources. (This is the "lazy" Chinese style)

This implementation in a multithreaded environment is unsafe, if multiple threads can enter at the same time if (uniqueInstance == null), and this time uniqueInstance is null, then there will be multiple threads execute uniqueInstance = new Singleton();the statement, which will lead to instantiate multiple uniqueInstance.

public class Singleton {
    
    

    private static Singleton uniqueInstance;

    private Singleton() {
    
    
    }

    public static Singleton getUniqueInstance() {
    
    
        if (uniqueInstance == null) {
    
    
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

Ⅱ Hungry Chinese Style-Thread Safety

The problem of lazy thread insecurity is mainly due to the uniqueInstance being instantiated multiple times. The method of directly instantiating uniqueInstance will not cause thread insecurity problems. The object is created when the class is loaded, and you come to me and return directly. Fragrant! simple!

However, the direct instantiation method also loses the resource saving benefits brought by delayed instantiation.

private static Singleton uniqueInstance = new Singleton();

Ⅲ Lazy man-thread safety

Only need to lock the getUniqueInstance() method, then only one thread can enter the method at a time point, thus avoiding multiple uniqueInstance instantiations.

But when a thread enters the method, other threads trying to enter the method must wait, even if the uniqueInstance has been instantiated. This will cause the thread to block for too long, so this method has performance problems and is not recommended. Think about it even if it has been instantiated, but every time I get in the method, it will be blocked synchronously. Who can stand it?

public static synchronized Singleton getUniqueInstance() {
    
    
    if (uniqueInstance == null) {
    
    
        uniqueInstance = new Singleton();
    }
    return uniqueInstance;
}

Ⅳ Double check lock-thread safety

uniqueInstance only needs to be instantiated once, after which it can be used directly. The lock operation only needs to be performed on the part of the code that is instantiated, and only when the uniqueInstance is not instantiated, does it need to be locked.

The double check lock first determines whether the uniqueInstance has been instantiated, and if it has not been instantiated, then the instantiation statement is locked.

public class Singleton {
    
    

    private volatile static Singleton uniqueInstance;

    private Singleton() {
    
    
    }

    public static Singleton getUniqueInstance() {
    
    
        if (uniqueInstance == null) {
    
    
            synchronized (Singleton.class) {
    
    
                if (uniqueInstance == null) {
    
    
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

Consider the following implementation, which uses only one if statement. In the case of uniqueInstance == null, if both threads execute the if statement, both threads will enter the if statement block. Although there are locking operation in the if block, but the two threads will execute uniqueInstance = new Singleton();this statement, but has a problem, it will be instantiated twice. Therefore, double check locks must be used, that is, two if statements are required: the first if statement is used to avoid the lock operation after the uniqueInstance has been instantiated, and the second if statement is locked, so it can only When one thread enters, there will be no instance when uniqueInstance == null and two threads will instantiate simultaneously.

if (uniqueInstance == null) {
    
    
    synchronized (Singleton.class) {
    
    
        uniqueInstance = new Singleton();
    }
}

It is also necessary for uniqueInstance to be modified with the volatile keyword. uniqueInstance = new Singleton();This code is actually executed in three steps:

  1. Allocate memory space for uniqueInstance
  2. Initialize uniqueInstance
  3. Point uniqueInstance to the allocated memory address

However, due to the nature of instruction rearrangement of the JVM, the execution order may become 1>3>2. There is no problem with instruction rearrangement in a single-threaded environment, but in a multi-threaded environment it will cause a thread to obtain an instance that has not yet been initialized. For example, thread T1 executes 1 and 3, and T2 finds that uniqueInstance is not empty after calling getUniqueInstance(), so it returns uniqueInstance, but uniqueInstance has not been initialized yet.

Using volatile can prohibit the reordering of JVM instructions and ensure normal operation in a multi-threaded environment.

Ⅴ Realization of static internal classes

When the Singleton class is loaded, the static inner class SingletonHolder is not loaded into memory. Only when you call getUniqueInstance()a method to trigger SingletonHolder.INSTANCEwhen SingletonHolder will be loaded at this time initialization INSTANCE instance, and to ensure INSTANCE JVM is instantiated only once ** (some students will ask, if multiple threads simultaneously calling SingletonHolder (), does not Is it safe? This involves in the case of multiple threads to initialize a class at the same time, only one thread will execute the () method of this class, and other threads need to block and wait until the active thread executes the () method) **.

This method not only has the benefit of lazy initialization, but also supports thread safety provided by the JVM.

public class Singleton {
    
    

    private Singleton() {
    
    
    }

    private static class SingletonHolder {
    
    
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getUniqueInstance() {
    
    
        return SingletonHolder.INSTANCE;
    }
}

Ⅵ Enumeration realization

public enum Singleton {
    
    

    INSTANCE;

    private String objName;


    public String getObjName() {
    
    
        return objName;
    }


    public void setObjName(String objName) {
    
    
        this.objName = objName;
    }


    public static void main(String[] args) {
    
    

        // 单例测试
        Singleton firstSingleton = Singleton.INSTANCE;
        firstSingleton.setObjName("firstName");
        System.out.println(firstSingleton.getObjName());
        Singleton secondSingleton = Singleton.INSTANCE;
        secondSingleton.setObjName("secondName");
        System.out.println(firstSingleton.getObjName());
        System.out.println(secondSingleton.getObjName());

        // 反射获取实例测试
        try {
    
    
            Singleton[] enumConstants = Singleton.class.getEnumConstants();
            for (Singleton enumConstant : enumConstants) {
    
    
                System.out.println("reflection:"+enumConstant.getObjName());
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}
firstName
secondName
secondName
reflection:secondName

This implementation can prevent reflection attacks. In other implementations, the access level of the private constructor can be set to public through the setAccessible() method, and then the constructor can be called to instantiate the object. If you want to prevent this kind of attack, you need to add in the constructor to prevent multiple instantiations Code. This implementation is guaranteed by the JVM to be instantiated only once, so the above reflection attack will not occur.

This implementation will not get multiple instances after multiple serialization and serialization. Other implementations need to use transient to modify all fields, and implement serialization and deserialization methods.

Reference link: Design Pattern-Singleton.md .
https://github.com/CyC2018/CS-Notes/blob/master/notes/Design Pattern-Singleton.md#ⅳ-Double check lock-thread safety

Guess you like

Origin blog.csdn.net/weixin_43957211/article/details/110539188