Seven Ways of Writing Singleton Patterns

Keywords: socket jvm Attribute Java

Article transferred from public account Invincible Baron | If anyone asks you about your singleton pattern again, throw him these seven ways of writing!

The singleton design pattern is one of the most basic and commonly used design patterns among the 23 design patterns. It is also one of the high frequency questions about the knowledge points of design pattern in the interview. Speaking of the writing of singleton pattern, in most cases, it may appear in our mind that "hungry man" and "lazy man" are the two writing methods. But today, Xiao Biao is going to introduce seven writing methods of singleton pattern. If the interviewer asks you about the singleton pattern, then throw them to him.

Next, let's get down to business. Let's introduce how to write these seven singleton patterns.

Hungry man

Hungry Chinese style is a classical way to realize the singleton pattern design. The implementation code is as follows:

//final does not allow inheritance
public final class SingleTonEhangshi {
    //Instance variables
    private byte[] data = new byte[1024];

    //Direct initialization when defining instance objects
    private static SingleTonEhangshi instance = new SingleTonEhangshi();

    //Privatization constructor, no external NEW allowed
    private SingleTonEhangshi() {

    }

    public static SingleTonEhangshi getInstance() {
        return instance;
    }
}

The key to hungry Chinese implementation is that instance is initialized directly as a class variable. If we actively use SingleToEhangshi class, instance instance will be created directly, including instance variables will also be initialized.

Instance, as a class variable, is collected into the < clinit > () method in the process of class initialization, which guarantees 100% synchronization, that is to say, instance can not be initialized twice in the case of multi-threaded. But since instance is loaded by ClassLoader for a long time before it is used, that means that the heap memory opened by instance instance instance will stay for a long time.

Generally speaking, if the member variables of a class are relatively small and the memory resources occupied are not much, it is not necessary to implement the singleton mode in a hungry way, but it can not be lazily loaded.

Slacker type

The so-called lazy style is to create when using class instances, that is to say, when I use it, I create it again, so that we can avoid creating classes in advance when initialization occupies memory space. The implementation code is as follows:

//final does not allow inheritance
public final class SingleTonLhangshi {
    //Instance variables
    private byte[] data = new byte[1024];

    //Define instances, but do not initialize them directly
    private static SingleTonLhangshi instance = null;

    //Privatization constructor, no external NEW allowed
    private SingleTonLhangshi() {

    }

    public static SingleTonLhangshi getInstance() {
        if (null == instance) {
            instance = new SingleTonLhangshi();
        }
        return instance;
    }
}

Class variable instance=null, so instances are not instantiated immediately when classes are initialized, but rather when getInstance() methods are called to determine whether instance instances are instantiated or not, and if there is no instantiation, private constructors are called to instantiate operations.

In multi-threaded environment, lazy-handed writing can cause multiple threads to see null==instance at the same time, which leads to instances being instantiated many times, thus failing to guarantee the uniqueness of the singleton.

Lazy Man + Synchronization Method

The lazy singleton implementation can ensure the lazy loading of instances, but it can not guarantee the uniqueness of instances. In multi-threaded environment, instance is to share data. When multiple threads access and use it, they need to ensure the synchronization of data. So if we need to ensure the uniqueness of lazy instance, we can achieve it by synchronization. The code is as follows:

//final does not allow inheritance
public final class SingleTonLhangshiSync {
    //Instance variables
    private byte[] data = new byte[1024];

    //Define instances, but do not initialize them directly
    private static SingleTonLhangshiSync instance = null;

    //Privatization constructor, no external NEW allowed
    private SingleTonLhangshiSync() {

    }

    //Add synchronization control to the getInstance method, and only one thread can enter at a time
    public static synchronized SingleTonLhangshiSync getInstance() {
        if (null == instance) {
            instance = new SingleTonLhangshiSync();
        }
        return instance;
    }
}

The method of lazy-man + data synchronization not only satisfies lazy loading but also guarantees 100% uniqueness of instance instance instance. However, the exclusiveness of synchronized keywords will result in the getInstance() method being accessed by only one thread at a time, which will result in poor performance.

Double-Check

Double-Check is a clever way to design. It provides an efficient data synchronization strategy, which is to lock the first initialization, and then allow multiple threads to call getInstance() methods simultaneously to get instances of classes. The code is as follows:

//final does not allow inheritance
public final class SingletonDoubleCheck {
    //Instance variables
    private byte[] data = new byte[1024];

    //Define instances, but do not initialize them directly
    private static SingletonDoubleCheck instance = null;

    Connection con;
    Socket socket;

    //Privatization constructor, no external NEW allowed
    private SingletonDoubleCheck(Connection con, Socket socket) {
        this.con = con;//Initialization
        this.socket = socket;//Initialization

    }
    public static SingletonDoubleCheck getInstance() {
        //When instance is null, it enters the synchronization code block. At the same time, the judgment avoids the need to enter the synchronization code block every time, which can improve the efficiency.
        if (null == instance) {
            //Only one thread can get the monitor associated with Singleton DoubleCheck. class
            synchronized (SingletonDoubleCheck.class) {
                //Determine if instance is null then create
                if (null == instance) {
                    instance = new SingletonDoubleCheck();
                }
            }
        }
        return instance;
    }
}

When two threads discover nullinstance is established, only one thread is eligible to enter the synchronization code block and complete the initialization of instance. The subsequent threads discover that nullinstance is not established without any operation. Later access to getInstance will no longer need data synchronization.

This method not only satisfies lazy loading, but also guarantees the uniqueness of instance instances. It also provides a more efficient data synchronization strategy that allows multiple threads to access getInstance at the same time. However, this approach may cause null pointer exceptions in the case of multi-threading, because if there is an initialization of other resources in the construction method of the above code, because there is instruction rearrangement in the JVM runtime, the order of these resources is not constrained by the preceding and the following relationship, then in the case of the JVM runtime, the null pointer exceptions may occur. In this case, it is most likely that instance is instantiated first, and con and socket are not instantiated, and the instance that is not instantiated will throw a null pointer exception when calling its method.

Volatile+Double-Check

In order to solve the null pointer problem caused by Double-Check instruction rearrangement, volatile keyword can be used to prevent such rearrangement. So the code only needs a little modification to satisfy the efficiency of single instance, lazy loading and instance under multi-threading. The code is as follows:

//final does not allow inheritance
public final class SingletonDoubleCheck {
    //Instance variables
    private byte[] data = new byte[1024];

    //Define instances, but do not initialize them directly
    private static volatile SingletonDoubleCheck instance = null;

    Connection con;
    Socket socket;

    //Privatization constructor, no external NEW allowed
    private SingletonDoubleCheck(Connection con, Socket socket) {
        this.con = con;//Initialization
        this.socket = socket;//Initialization

    }

    public static SingletonDoubleCheck getInstance() {
        //When instance is null, it enters the synchronization code block. At the same time, the judgment avoids the need to enter the synchronization code block every time, which can improve the efficiency.
        if (null == instance) {
            //Only one thread can get the monitor associated with Singleton DoubleCheck. class
            synchronized (SingletonDoubleCheck.class) {
                //Determine if instance is null then create
                if (null == instance) {
                    instance = new SingletonDoubleCheck();
                }
            }
        }
        return instance;
    }
}

Holder mode

The Holder approach relies entirely on the characteristics of class loading. The code is as follows:

//Inheritance is not allowed
public final class SingletonHolder {
    //Instance variables
    private byte[] data = new byte[1024];

    private SingletonHolder() {

    }

    //Hold instances of singleton classes in static inner classes and can be initialized directly
    private static class Holder {
        private static SingletonHolder instance = new SingletonHolder();
    }

    //Calling the getInstance method actually obtains Holder's instance static attribute
    public static SingletonHolder getInstance() {
        return Holder.instance;
    }
}

There is no static member of instance in the singleton class, but it is placed in the static inner class Holder, so the singleton class does not create an instance of Singleton Holder in the initialization process. The static variable of Singleton Holder is defined in the inner class Holder, and it is instantiated directly, only when Holder is active. An instance of Singleton Holder is created only when referenced.

The creation process of Singleton Holder instance is collected into <clinit>() method during Java program compilation. This method is also a synchronization method, which can ensure the visibility of memory, the order and atomicity of JVM instructions. Holder's singleton pattern design is one of the best designs, and it is also widely used at present.

Enumeration mode

Enumeration is also widely used in many open source frameworks. Enumeration types are not allowed to be inherited. It is also thread-safe and can only be instantiated once, but enumeration types cannot be lazy to load. With enumeration types, the code to implement the singleton pattern is as follows:

public class SingletonEnum {
    //Instance variables
    private byte[] data = new byte[1024];

    private SingletonEnum() {

    }

    //Use enumerations as Holder s
    private enum EnumHolder {
        INSTANCE;
        private SingletonEnum instance;

        EnumHolder() {
            this.instance = new SingletonEnum();
        }

        private SingletonEnum getInstance() {
            return instance;
        }
    }

    public static SingletonEnum getInstance() {
        return EnumHolder.INSTANCE.getInstance();
    }
}

These are the seven ways to write the singleton mode. Although the singleton mode is very simple, in the case of multi-threading, the singleton program we designed before may not be able to meet the characteristics of single instance, lazy loading and high performance.

Posted by Begbie on Mon, 23 Sep 2019 03:35:02 -0700