Byte the interviewer asked: write a single example pattern that you think is the best? So I wrote seven

Keywords: Java Singleton pattern

Interview question: write a single case model that you think is the best

Interview investigation point

Purpose of investigation: single case mode can investigate a lot of basic knowledge, so many interviewers will ask this question. Small partners should note that during the interview process, any topic that can examine the ability of job seekers from multiple dimensions will not be abandoned, especially the more general questions, such as "please talk about your understanding of xxx".

Scope of investigation: 1 to 5 years of working experience. With the improvement of experience, the deeper the investigation on this problem is.

background knowledge

Singleton mode is a software design mode, which belongs to the creation mode.

Its feature is to ensure that a class has only one instance and provide a global access point.

Based on this feature, the advantage of singleton mode is that it can avoid the memory consumption caused by frequent object creation, because it limits the creation of instances. In general, it has the following advantages:

  1. Control the use of resources, and control the concurrent access of resources through thread synchronization;

  2. Control the number of instances to save resources.

  3. As a communication medium, that is, data sharing, it can realize communication between multiple unrelated two threads or processes without establishing direct correlation.

In practical application, the most commonly used singleton mode is in the IOC container of Spring. For bean management, singleton is used by default. A bean will only create one object and store it in the built-in map. After that, the same object will be returned no matter how many times the bean is obtained.

Let's look at the design of singleton mode.

Singleton pattern design

Since you want to ensure that a class has only one instance during operation, you must not use the new keyword for instance.

Therefore, the first step must be to privatize the constructor of this class, which prevents the caller from creating an instance of this class.

Then, since the object cannot be instantiated externally, a global access entry must be provided after internal instantiation to obtain the globally unique instance of the class. Therefore, we can define a static variable inside the class to reference the unique instance as the access object for the externally provided instance. Based on these points, we can get the following design.

public class Singleton {
    // Static field reference unique instance:
    private static final Singleton INSTANCE = new Singleton();

    // The private constructor guarantees that the external cannot be instantiated:
    private Singleton() {
    }
}

Then, we need to give an external method to access the object INSTANCE instance INSTANCE instance. We can provide a static method

public class Singleton {
    // Static field reference unique instance:
    private static final Singleton INSTANCE = new Singleton();

    // Return an instance through a static method:
    public static Singleton getInstance() {
        return INSTANCE;
    }

    // The private constructor guarantees that the external cannot be instantiated:
    private Singleton() {
    }
}

This completes the design of the singleton mode. In summary, the singleton mode is divided into three steps.

  1. Use the private privatization construction method to ensure that the external cannot be instantiated;

  2. The unique instance is held through the private static variable to ensure global uniqueness;

  3. Return this unique instance through the public static method so that the external caller can get the instance.

Other implementations of singleton mode

Since singleton mode only needs to ensure that only unique instances will be generated during program operation, it means that singleton mode has more implementation methods.

  • Lazy singleton mode

  • Hungry Han single case mode

  • DCL double check single case

  • Static inner class

  • Enumeration singleton

  • Implementation of singleton based on container

Lazy singleton mode

Lazy means that the object instance is not created in advance, but created when needed. The code is as follows.

public class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

    // The synchronized method ensures the uniqueness of the singleton object in the case of multithreading
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

Among them, the synchronized synchronization keyword is added to the getInstance() method to avoid multi instance problems (thread safety problems caused by the parallel execution characteristics of threads) caused by calling the method at the same time in a multi-threaded environment.

advantage:   The singleton is instantiated only when it is used, which saves memory resources to a certain extent. Disadvantages:   Instantiate immediately when loading for the first time, and the response is slightly slow. Each call to getInstance() method will synchronize, which will consume unnecessary resources. This mode is generally not recommended.

DCL double check single case

DCL double check singleton mode is a performance optimized version based on hungry Han singleton mode.

/**
 * DCL Implement singleton mode
 */
public class Singleton {
    private static volatile Singleton instance = null;

    private Singleton() {
    }

    public static Singleton getInstance() {
        // The first layer is to avoid unnecessary synchronization
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {// The second layer is to create an instance in the case of null
                    instance = new Singleton();
                }
            }

        }
        return instance;
    }
}

As can be seen from the code, DCL mode has made two improvements:

  1. In the getInstance() method, the locking range of synchronized synchronization lock is reduced.

    Narrowing the scope of the lock can improve the performance. Think about it. In the original lazy mode, loading the synchronized keyword at the method level means that any caller needs to obtain the object instance, whether in a multi-threaded environment or a single threaded environment. However, adding this lock only needs to initialize the instance for the first time For subsequent access, you should directly return the instance object. Therefore, adding synchronized to the method level will inevitably bring performance overhead in a multithreaded environment.

    The transformation of DCL mode is to narrow the scope of locking. Only the instance object instance needs to be protected for the first initialization, and subsequent access does not need to compete for synchronization locks. Therefore, its design is:

    • First judge whether the instance instance is empty. If so, add a synchronized class level lock to protect the instance object instantiation process and avoid multi instance problems in a multi-threaded environment.

    • Then, within the synchronized synchronization keyword range, judge whether the instance instance is empty again. Similarly, it is also to avoid the multi instance problem when the previous thread has just completed initialization and the next thread enters the synchronization code block at the critical point.

  2. The volatile keyword is modified on the member variable instance to ensure visibility.

    This keyword is added to avoid the visibility problem caused by instruction reordering in the JVM. This problem is mainly reflected in the code instance=new Singleton(). Let's look at the bytecode of this code

     17: new           #3                  // class org/example/cl04/Singleton
     20: dup
     21: invokespecial #4                  // Method "<init>":()V
     24: putstatic     #2                  // Field instance:Lorg/example/cl04/Singleton;
     27: aload_0
     28: monitorexit
     29: goto          37
     32: astore_1
     33: aload_0
    

    Focus on the following instructions

    The invokespecial #4 instruction and astore_1 instruction allow reordering (the reordering problem will not be explained in this article and will be analyzed in the subsequent interview questions), that is, the execution sequence may be executed first by astore_1,   Execute after invokespecial #1.

    Reorder for two instruction operations that do not have dependencies, CPU, memory and JVM, in order to optimize program execution performance, the execution instructions will be reordered. In other words, the execution order of the two instructions does not necessarily follow the programming order.

    Because after the address of the object is created on the heap, the address has been determined, and there is no logical relationship between "establishing a reference association between the Singleton instance in the stack and the object on the heap" and "assigning the member variable in the object".

    So the cpu can execute out of order, as long as the final result of the program is consistent.

    In this case, there is no problem in single thread, but errors will occur in multi thread.

    Just imagine that in DCL, thread A just executed the new #4 instruction when it created the new object, and then executed astore instead of invokespecial #4 instruction_ 1, that is, an instruction reordering has occurred.

    At this time, thread B enters getInstance() and finds that instance is not empty (because there is already a reference pointing to the object, but it has not had time to assign a value to the member variable in the object), and then thread B directly return s a "semi initialized" object (the object has not been completely created).

    Therefore, in DCL, the volatile keyword needs to be added to instance, because volatile has a feature called memory barrier in the JVM layer, which can prevent instruction reordering and ensure the correctness of the program.

    • new #3  : This line of instructions means that a space is opened up at an address on the heap as a Singleton object

    • invokespecial #4  : This line of instructions is to assign the member variables in the object

    • astore_ one  : This line of instructions means to establish a reference association between the Singleton instance in the stack and the object on the heap

Advantages and disadvantages of DCL mode:

Advantages: high resource utilization. It can not only initialize the instance when necessary, but also ensure thread safety. At the same time, calling getInstance() method does not carry out synchronization lock, which is highly efficient. Disadvantages: the first load is slightly slow and occasionally fails due to the Java memory model. There are also some defects in the high concurrency environment, although the probability of occurrence is very small.

DCL mode is the most used single instance mode. Unless the code is complex in the concurrent scenario, this mode can basically meet the requirements.

Hungry Han single case mode

No singleton instance is created when the class is loaded. It is created only when the instance is requested for the first time, and only after the first creation, the instance of this class will not be created in the future.

/**
 * Hungry Han style implementation of singleton mode
 */
public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {
    }

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

The attribute modified by the static keyword indicates that this member belongs to the class itself, not an instance. At runtime, the Java virtual machine allocates memory for static variables only once, and completes the memory allocation of static variables during class loading.

Therefore, the object instance is created when the class is loaded, and the instance can be obtained directly during subsequent access.

The advantages and disadvantages of this model are also very obvious.

Advantages: thread safety, no need to consider concurrency safety.

Disadvantages: it wastes memory space. No matter whether the object is used or not, memory space will be allocated in advance at startup.

Static inner class

The static internal class is based on the optimization of starving Chinese mode.

The instance will not be initialized when the Singleton class is loaded for the first time. Only when the getInstance() method is called for the first time, the virtual opportunity loads the SingletonHolder class and initializes the instance. instance   The uniqueness and thread safety of the creation process are guaranteed by the JVM.

/**
 * The static inner class implements the singleton pattern
 */
public class Singleton {
  private Singleton() {
  }

  public static Singleton getInstance() {
    return SingletonHolder.instance;
  }

  /**
     * Static inner class
     */
  private static class SingletonHolder {
    private static Singleton instance = new Singleton();
  }
}

This method not only ensures thread safety and the uniqueness of the singleton object, but also delays the initialization of the singleton. It is recommended to use this method to implement the singleton mode.

Static internal classes will not be loaded due to the loading of external classes. At the same time, the loading of static internal classes does not need to be attached to external classes. They can be loaded only when in use, but external classes will also be loaded during the loading of static internal classes

Knowledge point: if static is used to modify an inner class, it is a static inner class. This inner class belongs to the outer class itself, but it does not belong to any object of the outer class. Therefore, inner classes decorated with static are called static inner classes. Static inner classes have the following rules:

  • Static internal classes cannot access instance members of external classes, but only class members of external classes.

  • The external class can use the class name of the static internal class as the caller to access the class members of the static internal class, or use the static internal class object to access its instance members.

Advantages of static internal class singleton:

  • Object creation is thread safe.

  • Support delayed loading.

  • There is no need to lock when acquiring objects.

This is one of the more commonly used modes.

Implementation of singleton based on enumeration

Using enumeration to implement singleton is the simplest way. This implementation ensures the thread safety and uniqueness of instance creation through the characteristics of Java enumeration types.

public enum SingletonEnum {

    INSTANCE;

    public void execute(){
        System.out.println("begin execute");
    }

    public static void main(String[] args) {
        SingletonEnum.INSTANCE.execute();
    }
}

Implementing a singleton based on enumeration will find that it does not require the operations described above

  1. Construction method privatization

  2. Instantiated variable reference privatization

  3. Methods for obtaining instances are

Enumerating in this way is actually not safe, because privatized constructs cannot resist reflection attacks

This method is advocated by Effective Java author Josh Bloch. It can not only avoid the problem of multi-threaded synchronization, but also prevent deserialization and re creation of new objects. It can be said to be a very strong barrier.

Implementation of singleton based on container

The following code demonstrates a container based approach to managing singletons.

import java.util.HashMap;
import java.util.Map;
/**
 * The container class implements the singleton pattern
 */
public class SingletonManager {
    private static Map<String, Object> objMap = new HashMap<String, Object>();

    public static void regsiterService(String key, Object instance) {
        if (!objMap.containsKey(key)) {
            objMap.put(key, instance);
        }
    }

    public static Object getService(String key) {
        return objMap.get(key);
    }
}

SingletonManager can manage multiple singleton types. During program initialization, multiple singleton types are injected into a uniformly managed class. When used, the object of the corresponding type of the object is obtained according to the key. In this way, operations can be obtained through a unified interface, which hides the specific implementation and reduces the coupling degree.

On the destruction of singleton mode

When analyzing the singleton mode implemented by enumeration classes, a problem was mentioned earlier, that is, the privatized structure will be destroyed by reflection, resulting in multi instance problems.

public class Singleton {

    private static volatile Singleton instance = null;

    private Singleton() {
    }

    public static Singleton getInstance() {
        // The first layer is to avoid unnecessary synchronization
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {// The second layer is to create an instance in the case of null
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) throws Exception{
        Singleton instance=Singleton.getInstance();
        Constructor<Singleton> constructor=Singleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton refInstance=constructor.newInstance();
        System.out.println(instance);
        System.out.println(refInstance);
        System.out.println(instance==refInstance);

    }
}

The operation results are as follows

org.example.cl04.Singleton@29453f44
org.example.cl04.Singleton@5cad8086
false

Because reflection can destroy the private feature, any single instance mode implemented through the private privatization structure can be destroyed by reflection, resulting in multi instance problems.

Some people may ask, why should we destroy the single case when we are free? Will there be no problem accessing directly based on this portal?

Theoretically, it is, but what if you encounter the following situation?

The following code demonstrates the serialization and deserialization of Singleton through object stream.

public class Singleton implements Serializable {

    private static volatile Singleton instance = null;

    private Singleton() {
    }

    public static Singleton getInstance() {
        // The first layer is to avoid unnecessary synchronization
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {// The second layer is to create an instance in the case of null
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) throws Exception {
        Singleton instance=Singleton.getInstance();
        ByteArrayOutputStream baos=new ByteArrayOutputStream();
        ObjectOutputStream oos=new ObjectOutputStream(baos);
        oos.writeObject(instance);
        ByteArrayInputStream bais=new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois=new ObjectInputStream(bais);
        Singleton ri=(Singleton) ois.readObject();
        System.out.println(instance);
        System.out.println(ri);
        System.out.println(instance==ri);
    }
}

The operation results are as follows

org.example.cl04.Singleton@36baf30c
org.example.cl04.Singleton@66a29884
false

It can be seen that the serialization method will also destroy the singleton pattern.

Destruction test of enumeration class singleton

Some people may ask, can't enumeration be destroyed?

We can try. The code is as follows.

public enum SingletonEnum {

    INSTANCE;

    public void execute(){
        System.out.println("begin execute");
    }

    public static void main(String[] args) throws Exception{
        SingletonEnum instance=SingletonEnum.INSTANCE;
        Constructor<SingletonEnum> constructor=SingletonEnum.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        SingletonEnum refInstance=constructor.newInstance();
        System.out.println(instance);
        System.out.println(refInstance);
        System.out.println(instance==refInstance);
    }
}

The operation results are as follows

Exception in thread "main" java.lang.NoSuchMethodException: org.example.cl04.SingletonEnum.<init>()
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.getDeclaredConstructor(Class.java:2178)
    at org.example.cl04.SingletonEnum.main(SingletonEnum.java:15)

From the error point of view, does it seem that there is no empty constructor? There is no proof that reflection cannot destroy a single case.

The following is the source code of Enum. All enumeration classes inherit the abstract class Enum.

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {
    /**
     * The name of this enum constant, as declared in the enum declaration.
     * Most programmers should use the {@link #toString} method rather than
     * accessing this field.
     */
    private final String name;

    /**
     * Returns the name of this enum constant, exactly as declared in its
     * enum declaration.
     *
     * <b>Most programmers should use the {@link #toString} method in
     * preference to this one, as the toString method may return
     * a more user-friendly name.</b>  This method is designed primarily for
     * use in specialized situations where correctness depends on getting the
     * exact name, which will not vary from release to release.
     *
     * @return the name of this enum constant
     */
    public final String name() {
        return name;
    }

    /**
     * The ordinal of this enumeration constant (its position
     * in the enum declaration, where the initial constant is assigned
     * an ordinal of zero).
     *
     * Most programmers will have no use for this field.  It is designed
     * for use by sophisticated enum-based data structures, such as
     * {@link java.util.EnumSet} and {@link java.util.EnumMap}.
     */
    private final int ordinal;

    /**
     * Returns the ordinal of this enumeration constant (its position
     * in its enum declaration, where the initial constant is assigned
     * an ordinal of zero).
     *
     * Most programmers will have no use for this method.  It is
     * designed for use by sophisticated enum-based data structures, such
     * as {@link java.util.EnumSet} and {@link java.util.EnumMap}.
     *
     * @return the ordinal of this enumeration constant
     */
    public final int ordinal() {
        return ordinal;
    }

    /**
     * Sole constructor.  Programmers cannot invoke this constructor.
     * It is for use by code emitted by the compiler in response to
     * enum type declarations.
     *
     * @param name - The name of this enum constant, which is the identifier
     *               used to declare it.
     * @param ordinal - The ordinal of this enumeration constant (its position
     *         in the enum declaration, where the initial constant is assigned
     *         an ordinal of zero).
     */
    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }
}

This class has a unique constructor that accepts two parameters: name and ordinal

Let's try to create an example through this construction method. The demonstration code is as follows.

public enum SingletonEnum {

    INSTANCE;

    public void execute(){
        System.out.println("begin execute");
    }

    public static void main(String[] args) throws Exception{
        SingletonEnum instance=SingletonEnum.INSTANCE;
        Constructor<SingletonEnum> constructor=SingletonEnum.class.getDeclaredConstructor(String.class,int.class);
        constructor.setAccessible(true);
        SingletonEnum refInstance=constructor.newInstance("refinstance",2);
        System.out.println(instance);
        System.out.println(refInstance);
        System.out.println(instance==refInstance);
    }
}

Run the above code and the execution result is as follows

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
    at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
    at org.example.cl04.SingletonEnum.main(SingletonEnum.java:17)

From the error information, we successfully obtained the Constructor, but reported an error in the newInstance.

Locate the source location where the error occurred.

if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile

From this Code: (clazz. Getmodifiers() & modifier. ENUM)= 0 Description: when creating an object through newInstance, reflection will check whether the class is ENUM decorated. If so, an exception will be thrown and reflection fails. Therefore, enumeration type is absolutely safe for reflection.

Since reflection cannot be destroyed? What about serialization? Let's try again

public enum SingletonEnum {

    INSTANCE;

    public void execute(){
        System.out.println("begin execute");
    }
    public static void main(String[] args) throws Exception{
        SingletonEnum instance=SingletonEnum.INSTANCE;
        ByteArrayOutputStream baos=new ByteArrayOutputStream();
        ObjectOutputStream oos=new ObjectOutputStream(baos);
        oos.writeObject(instance);
        ByteArrayInputStream bais=new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois=new ObjectInputStream(bais);
        SingletonEnum ri=(SingletonEnum) ois.readObject();
        System.out.println(instance);
        System.out.println(ri);
        System.out.println(instance==ri);
    }
}

The operation results are as follows

INSTANCE
INSTANCE
true

Therefore, we can draw a conclusion that enumeration type is the only design pattern among all singleton patterns that can avoid multi instance problems caused by reflection corruption.

To sum up, it can be concluded that enumeration is the best practice to implement singleton mode. After all, using it is all good:

  1. Reflection safety

  2. Serialization / deserialization security

  3. Simple writing

Problem solving

Interview question: write a single case model that you think is the best

Everyone must have an answer to this question. Enumeration is the best way to realize singleton.

Of course, the answer should be explained from an all-round perspective.

  1. Concept of singleton pattern

  2. What are the ways to implement a singleton

  3. Advantages and disadvantages of each singleton mode

  4. The best singleton mode, and why do you think it is the best?

Problem summary

The singleton mode looks simple, but there are still many knowledge points to learn the best.

For example, it involves thread safety issues, characteristics of static methods and static member variables, enumeration, reflection, etc.

I want to go back to the past. We only use jsp/servlet. We don't have so much messy knowledge. We just want to be a simple programmer.

  Small partners who need java learning materials can help like + forward. After paying attention to the small compilation, scan the code below   You can download a set of PDFJava core interview questions of this comprehensive system for free~

Posted by Alexhoward on Thu, 04 Nov 2021 12:18:46 -0700