Dahua Design Pattern - Singleton Pattern

Singleton

Intent

Make sure there is only one instance of a class and provide a global access point to that instance.

Class Diagram

Implemented using a private constructor, a private static variable, and a public static function.

The private constructor ensures that the object instance cannot be created through the constructor, and the only private static variable can only be returned through the public static function.


Implementation

Ⅰ Lazy style-thread unsafe

In the following implementation, the private static variable uniqueInstance is lazily instantiated. The advantage of this is that if the class is not used, uniqueInstance will not be instantiated, thus saving resources.

This implementation is unsafe in a multi-threaded environment. If multiple threads can enter at the same time if (uniqueInstance == null)and uniqueInstance is null at this time, then multiple threads will execute uniqueInstance = new Singleton();the statement, which will result in multiple instantiations of 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

Thread insecurity is mainly caused by uniqueInstance being instantiated multiple times. Direct instantiation of uniqueInstance will not cause thread insecurity.

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

private static Singleton uniqueInstance = new Singleton();

Ⅲ Lazy style-thread safety

Only the getUniqueInstance() method needs to be locked, so that only one thread can enter the method at a point in time, thus avoiding the need to instantiate uniqueInstance multiple times.

But when a thread enters this method, other threads trying to enter this method must wait, even if uniqueInstance has been instantiated. This will cause the thread to block for too long, so this method has performance issues and is not recommended.

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 and can be used directly afterwards. The locking operation only needs to be performed on the part of the code that is instantiated. Locking is only required when uniqueInstance has not been instantiated.

Double check lock first determines whether uniqueInstance has been instantiated. 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 is a locking operation in the if statement block, both threads will execute uniqueInstance = new Singleton();this statement. It is just a matter of order, so it will be instantiated twice. Therefore, double check locking must be used, that is, two if statements need to be used: the first if statement is used to avoid the locking operation after uniqueInstance has been instantiated, and the second if statement is locked, so only If one thread enters, there will be no uniqueInstance == null when two threads perform instantiation operations at the same time.

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

It is also necessary to modify uniqueInstance 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 instruction rearrangement feature of the JVM, the execution order may become 1>3>2. Instruction reordering does not cause problems 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 T 1 executes 1 and 3. At this time, T 2 calls getUniqueInstance() and finds that uniqueInstance is not empty, so uniqueInstance is returned, but uniqueInstance has not been initialized at this time.

Using volatile can prohibit JVM instruction reordering to ensure normal operation in a multi-threaded environment.

Ⅴ Static inner class implementation

When the Singleton class is loaded, the static inner class SingletonHolder is not loaded into memory. The SingletonHolder will be loaded only when getUniqueInstance()the method is called SingletonHolder.INSTANCEto trigger the INSTANCE instance, and the JVM can ensure that the INSTANCE is only instantiated once.

This approach not only has the benefit of delayed initialization, but also provides thread-safety support 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 implementation

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(enumConstant.getObjName());
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}
firstName
secondName
secondName
secondName

This implementation prevents 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 is called to instantiate the object. If you want to prevent this attack, you need to add a function in the constructor to prevent multiple instantiations. code. This implementation is guaranteed by the JVM to be instantiated only once, so the above-mentioned reflection attacks will not occur.

This implementation won't get multiple instances after serializing and deserializing multiple times. Other implementations need to use transient to modify all fields and implement serialization and deserialization methods.

Examples

  • Logger Classes
  • Configuration Classes
  • Accesing resources in shared mode
  • Factories implemented as Singletons

JDK

Guess you like

Origin blog.csdn.net/weixin_42917352/article/details/131227604