This paper summarizes the singleton pattern in the design pattern.

The hungry type

public class Singleton{
    // Class is initialized when it is loaded
    private static final Singleton instance = new Singleton();
    
    private Singleton(a){}

    public static Singleton getInstance(a){
        returninstance; }}Copy the code

This approach is very simple, since instances are declared static and final and therefore initialized when the class is first loaded into memory, the process of creating an instance is thread-safe. However, this method is not lazy loading, relatively consume memory resources. In addition, the hungry the creation method in some scenarios will not be able to use: such as the creation of a Singleton instance is depend on the parameters or configuration file, the getInstance () method must be called before a set the parameters to it, as the single example of writing can’t use.

Lazy, thread unsafe (incorrect)

public class Singleton {
    private static Singleton instance;
    private Singleton (a){}

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

This code is in lazy-loading mode, which works fine in a single thread, but when multiple threads call getInstance(), thread-safety issues arise, meaning that multiple instances can be created.

Lazy, thread-safe

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

The simplest solution is above the entire getInstance () method for synchronous, such can also meet the requirements in a multithreaded scene, but each thread each run to this method will try to get the lock, performance, consuming only instance is created for the first time synchronization is significant, this leads to a double check.

Double check lock optimizes performance

public static Singleton getSingleton(a) {
    if (instance == null) {                         //Single Checked
        synchronized (Singleton.class) {
            if (instance == null) {                 //Double Checked
                instance = newSingleton(); }}}return instance ;
}
Copy the code

Instead of synchronizing the entire method, the double detection lock synchronizes code blocks, and adds a layer of detection to the outside of the synchronized code block to avoid a meaningless performance drain. However, there is a problem with this code. The problem is caused by both the outer check if (instance == NULL) and the inner instantiation statement instance = new Singleton(). Because the instantiation process is not an atomic operation, it actually roughly does the following in the JVM:

  1. Allocate memory to instance
  2. Call the constructor of the Singleton to initialize a member variable
  3. Reference instance to the allocated memory space (instance is not null after this step)

However, due to the optimization of instruction rearrangement at both Jvm and CPU levels, the above operations may not be executed sequentially in concurrent cases, that is, 1-3-2 order may occur to return the instance that has not been initialized, resulting in an error. To solve this problem, you simply declare instance as volatile.

public class Singleton {
    private volatile static Singleton instance; // Declare as volatile
    private Singleton (a){}

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

Volatile has two layers of semantics:

  1. The voaltile variable is in main memory, not in the thread’s private working memory, ensuring that different threads operate on the variable so that when one thread changes the value of a variable, the new value is immediately visible to other threads.
  2. Local disable instruction reordering.

Note that prior to Java 5, there was a flaw in the DESIGN of the JVM that prevented instruction reordering even with volatile variables, but this has been addressed since Java 5.

Static inner class

public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (a){}  
    public static final Singleton getInstance(a) {  
        returnSingletonHolder.INSTANCE; }}Copy the code

This approach still uses the JVM’s own mechanism to ensure thread-safety issues; Because SingletonHolder is private, there is no way to access it other than getInstance(), so it is lazy; There is no synchronization when reading instances at the same time, no performance defects; Nor does it depend on the JDK version.

The enumeration

public enum EasySingleton{
    INSTANCE;
}
Copy the code

Enumerations are probably the cleanest method, and we can access instances via easysingleton.instance, which is much simpler than calling getInstance(). Creating enumerations is thread-safe by default, so you don’t need to worry about double Checked locking, and it also prevents deserialization from causing new objects to be recreated. For other implementations of the singleton pattern, if you want both to be serializable and to deserialize to the same object, you must implement the readResolve method. However, this approach is not lazy loading. In fact, the example is initialized when the enumeration class is loaded.

conclusion

In general, there are five ways to write a singleton: hungry, lazy, double-checked locks, static inner classes, and enumerations.

In general, it is best to use hanhanian expressions directly. Static inner classes tend to be used when lazy initialization is required, and singletons are used when deserialization is involved in creating objects.