“This is the 11th day of my participation in the First Challenge 2022. For details: First Challenge 2022”

1. Mode introduction

Singleton Patter, the simplest of GOF’s 23 design patterns, is the creation pattern. The definition of a singleton is also easy to understand: “A pattern that allows only one instance of a class to be created is called a singleton.”

2. Application scenarios

It is easy to imagine from the definition of the singleton pattern that when a class needs to be unique in the entire system, we can consider using the singleton pattern to design the class in order to reduce the unnecessary performance cost of frequently creating and recycling objects.

3. Implementation method

There are many ways to implement the singleton pattern, the most important of which are lazy and hungry.

As the name implies, slacker means a man is so lazy that HE only sits in when you ask me to eat. Applied to the singleton pattern, this means that objects are lazily loaded and instantiated only when they are used.

In the same way, hungry han is when a man is so hungry that he is already at his table waiting for dinner long before he is called to it. Applied to the singleton pattern, the object is instantiated at system startup.

3.1 Hungry (static variable)

The idea of realizing the singleton pattern based on hanchian is to instantiate the class when it is loaded by the class loader.

public class Singleton4 {

    private static Singleton4 instance = new Singleton4();

    private Singleton4(a) {}public static Singleton4 getInstance(a) {
        returninstance; }}Copy the code
  • Advantages: Avoiding multithreading synchronization based on class loading mechanism, fast object acquisition (directly return the object)
  • Disadvantages: Creating an instance at the time of class loading leads to slow class loading, which does not achieve the effect of lazy loading. At the same time, it is not sure whether there is another method to cause secondary class loading, breaking the singleton limit

3.2 Hungry (enumeration class)

Another, less common, hanch-based singleton is implemented through enumerated classes.

public enum Singleton6 {

    INSTANCE;

    // Business method
    public void serviceMethod(a) {}}Copy the code
  • Advantages: Simple, enumeration instances are thread-safe and singleton by default, and deserialization does not generate new objects
  • Disadvantages: Not applicable to scenarios where singleton property values need to be dynamically set in configuration file scenarios, and inheritance is not supported

3.3 Lazy (Thread Unsafe)

public class Singleton1 {

    // Declare the singleton private and do not instantiate
    private static Singleton1 instance = null;

    // Declare that the constructor access of the class is private, making it impossible to instantiate outside of this class
    private Singleton1(a) {}// The method to get the singleton
    public static Singleton1 getInstance(a) {
        // Check whether the object is instantiated. If it is not instantiated, a new object is created
        if (null == instance) {
            instance = new Singleton1();
        }
        returninstance; }}Copy the code

The thread-unsafe lazy singleton pattern specifies that an object is instantiated only on the first call to the getInstance method, i.e. lazy loading is supported.

The reason why this implementation is not thread safe is that in a multi-threaded environment, there may be a case where both threads determine whether the object has been instantiated and are true, i.e. :

Singleton1#getInstance ->> Singleton1#getInstance: Singleton1#getInstance ->> Check whether the object is instantiated: Singleton1#getInstance: thread A: Singleton1#getInstance: thread B: Singleton1#getInstance: thread A: Singleton1#getInstance: thread B: Singleton1#getInstance: thread A: Singleton1#getInstance: Thread B: Singleton1#getInstance ->> Thread B: Singleton1#getInstance ->> Thread B: Singleton1#getInstance

As shown in the figure above, in a multithreaded environment, since the getInstance method is not atomic, two threads can enter the method at the same time, potentially resulting in multiple instantiated objects.

3.4 Lazy (Thread Safety)

In order to solve the defects of the first singleton implementation, the second singleton implementation is thread-safe slob with rough locking.

public class Singleton2 {

    // Declare the singleton private and do not instantiate
    private static Singleton2 instance = null;

    // Declare the constructor of the class private, making it impossible to instantiate outside of this class
    private Singleton2(a) {}// The method to get the singleton
    public static synchronized Singleton2 getInstance(a) {
        // Check whether the object is instantiated. If it is not instantiated, a new object is created
        if (null == instance) {
            instance = new Singleton2();
        }
        returninstance; }}Copy the code

The only difference between the second implementation and the first is that in order to keep the getInstance method thread-safe, a synchronized lock is added to the modifier to ensure that only one thread can enter the method at a time.

However, with synchronized, the overhead increases and the getInstance method becomes inefficient.

3.5 Lazy (Double Check lock)

In order to solve the problem of high overhead and low efficiency of synchronous Lock in the second thread-safe lazy singleton implementation, Double Check Lock (DCL) can be used to solve the problem.

public class Singleton3 {

    // Declare the singleton private and use the volatile keyword to ensure visibility
    private static volatile Singleton3 instance = null;

    // Declare the constructor of the class private, making it impossible to instantiate outside of this class
    private Singleton3(a) {}// The method to get the singleton
    public static Singleton3 getInstance(a) {
        // Check whether the object is instantiated. If it is not instantiated, a new object is created. The first check is for unnecessary synchronization
        if (null == instance) {
            // Lock to create instance
            synchronized (Singleton3.class) {
                // Check again to see if the instance is instantiated
                if (null == instance) {
                    instance = newSingleton3(); }}}returninstance; }}Copy the code

The idea of the DCL is to allow instantiation only if the object has not been instantiated. Second, before preparing to instantiate the object, add synchronize synchronization lock to the class and then check whether the object is instantiated again.

Dcl-based optimization has high resource utilization, meets lazy loading, and reduces the time consuming of calling getInstance method to obtain instances. However, DCL may fail (when the CPU is always busy).

3.6 Lazy (Static Inner Class)

There is another recommended way to write a lazy singleton: a static inner class.

public class Singleton5 {

    // Declare the constructor of the class private, making it impossible to instantiate outside of this class
    private Singleton5(a) {}// The method to get the singleton
    public static Singleton5 getInstance(a){
        return SingletonHolder.instance;
    }

    // Static inner class. When getInstance is called, load the static inner class and create the instance
    private static class SingletonHolder {
        private static final Singleton5 instance = newSingleton5(); }}Copy the code

Static inner class form is the declaration and instantiation of a singleton object in the static inner class of the singleton class.

The singleton class is not initialized when the class is loaded for the first time. Instead, when getInstance is called for the first time, the virtual machine loads the static inner class SingletonHolder and instantiates the singleton object. In this way, thread safety and class uniqueness can be ensured.

3.7 Realizing singletons based on Containers

public class Singleton7 {

    private static Map<String, Object> objMap = new HashMap<>();

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

    public static Object getService(String key) {
        returnobjMap.get(key); }}Copy the code

For example, based on the non-repeatable nature of the HashMap key, we can implement a simple singleton pattern. But the biggest problem with this demo is thread insecurity.

Of course, we can refer to the singleton pattern of Spring BeanFactory implementation, which has withstood the test of the majority of users and is more worthy of reference.

4. Verify the singleton pattern

We now know the singleton pattern has not more than seven kinds of way, but they still need to verify whether, at the same time also need to confirm the singleton pattern is whether there will be some concerns, so we will proceed the verification of the singleton pattern, the main idea is based on the I/O streams will first singleton object serialization to a local file, then read the file deserialized to program, Check whether the two objects are the same object.

4.1 Verifying the Singleton Mode

The code to verify the singleton pattern is as follows:

public class SingletonSerializableCheck {

    The process of converting an object to a sequence of bytes is called serialization of the object@param instance
    * @param filePath
    * @throws Exception
    */
    private static void writeEnum(Object instance, String filePath) throws Exception {
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(new File(filePath)));
        outputStream.writeObject(instance);
        outputStream.close();
    }

    /** * Singleton object deserialization * The process of restoring a sequence of bytes to an object is called object deserialization **@param filePath
    * @return Object
    * @throws Exception
    */
    private static Object readEnum(String filePath) throws Exception {
        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File(filePath)));
        return inputStream.readObject();
    }
  
  	public static void main(String[] args) throws Exception {
        // The singleton object being detected
      	// Here singleton1.getInstance () needs to change based on each singleton implementation
        Object checkedSingletonObj = Singleton1.getInstance();
        // File path
        String filePath = "SingletonEnumCheck.dat";
        // Serialize the singleton
        writeEnum(checkedSingletonObj, filePath);
        Object afterSerializableObj = readEnum(filePath);

      	// out: true, indicating that the enumeration class is also a singleton after deserialization; False: deserialization generates a new objectSystem.out.println(checkedSingletonObj == afterSerializableObj); }}Copy the code
  • Other singletons would deserialize to produce two singletons, changing the validation code declaration singletons toObject checkedSingletonObj = Singleton1.getInstance();False to indicate that deserialization generates a new object.

Note that before performing this validation method, you need to ensure that the above seven singleton classes implement the Serializable interface, otherwise serialization exceptions will occur. After verification by this method, the output result is as follows:

  • Hungry (static variable) : false
  • Hungry (enumeration class) : true
  • Lazy (thread unsafe) : false
  • Lazy (thread-safe) : false
  • Lazy (double-checked lock) : false
  • Lazy (static inner class) : false
  • Implement singletons based on containers: false

As you can see, except for the singleton pattern implemented based on enumeration, which remains the same object after serialization and deserialization, all methods generate new objects, which definitely breaks the singleton nature.

4.2 Reasons why deserialization destroys singletons

When the file is read and deserialized, the resulting object is not the same object as the original singleton. This is actually a problem caused by deserialization of the I/O stream.

Following Java. IO. ObjectInputStream# readObject () method in the process, actually there is a logic:

java.io.ObjectInputStream#readObject() -> java.io.ObjectInputStream#readObject(java.lang.Class<? >)501Line - > Java. IO. ObjectInputStream# readObject01665Line - > Java. IO. ObjectInputStream# readOrdinaryObject2167Line Object obj.try {
    // Whether to create an instance based on reflection
    obj = desc.isInstantiable() ? desc.newInstance() : null;
  } catch (Exception ex) {
    throw (IOException) new InvalidClassException(
      desc.forClass().getName(),
      "unable to create instance").initCause(ex);
  }
Copy the code

To deserialize the obj object to be returned, the first step is to determine whether the singleton class can be instantiated:

desc.isInstantiable()

// java.io.ObjectStreamClass#isInstantiable
boolean isInstantiable(a) {
  requireInitialized();
  return(cons ! =null);
}

The cons variable describes the default constructor information for the class
privateConstructor<? > cons;/ / initialization method of cons - > Java. IO. ObjectStreamClass# getSerializableConstructor
Copy the code

If a singleton class has a suitable no-argument constructor and can be instantiated, ObjectStreamClass creates a new instance of the singleton class by reflection and returns it as the result of deserialization, which is why the deserialized object is inconsistent with the original.

4.3 How to Avoid Deserialization from destroying singletons

Following Java. IO. ObjectInputStream# readObject () method in the process, I found that there is a code snippet:

java.io.ObjectInputStream#readObject() -> java.io.ObjectInputStream#readObject(java.lang.Class<? >)501Line - > Java. IO. ObjectInputStream# readObject01665Line - > Java. IO. ObjectInputStream# readOrdinaryObject2190lineif(obj ! =null &&
    handles.lookupException(passHandle) == null &&
    // Determine whether a readResolve method exists on a singleton class
    desc.hasReadResolveMethod())
{
  // Call the readResolve method of the target class to return the req object
  Object rep = desc.invokeReadResolve(obj);
  if (unshared && rep.getClass().isArray()) {
    rep = cloneArray(rep);
  }
  if(rep ! = obj) {// Filter the replacement object
    if(rep ! =null) {
      if (rep.getClass().isArray()) {
        filterCheck(rep.getClass(), Array.getLength(rep));
      } else {
        filterCheck(rep.getClass(), -1); }}// Change the deserialization target object to req objecthandles.setObject(passHandle, obj = rep); }}Copy the code

This code snippet means that if the readResolve method exists on the current singleton, reflection calls that method to get a REQ object, changing the deserialized object to a REQ object.

As to why is readResolve method, we can have a look at the Java. IO. ObjectStreamClass# hasReadResolveMethod judgment, it is through a readResolveMethod field to judge, The initialization code for this property is as follows:

java.io.ObjectStreamClass#ObjectStreamClass(java.lang.Class<? >)533Line getInheritableMethod (cl,"readResolve".null, Object.class);
Copy the code

The readResolve method is defined with a magic value in this line of code.

With this information in mind, the way to avoid deserializing broken singletons is easy: we just define a readResolve method in our singletons class to control deserialization of the object so that the deserialization returns the same object as the original.

/** * control deserialization, Prevent deserialization from generating new objects * * @return Object * @throws ObjectStreamException */ Private Object readResolve() throws ObjectStreamException {// return instance; }Copy the code

After adding the method to all of the singleton classes, you run the validation code again and all of the results return true, confirming that the new readResolve method avoids deserializing broken singletons.

4.4 Reflection failure singleton

In fact, in addition to deserialization breaking singletons, ordinary singletons can also be reflected to produce new objects, as shown in the following code:

public static void main(String[] args) throws Exception {
  // Get the singleton
  Singleton1 s1 = Singleton1.getInstance();

  // Get the constructor, set access permissions, and create a new instance
  Constructor<Singleton1> constructor = Singleton1.class.getDeclaredConstructor();
  constructor.setAccessible(true);
  Singleton1 s2 = constructor.newInstance();

  System.out.println(s1 == s2);
}
Copy the code

The program outputs the result: false, proving that reflection generates new objects and breaks the singleton.

The way to avoid this problem is very simple. From the source, reflection takes the constructor of the singleton class and creates a new instance, so just check the singleton object in the constructor of the singleton class, that is:

private Singleton1(a) {
  // singleton check to avoid reflection to create new instances
  if (instance == null) {
    throw new RuntimeException("Singleton already exists!"); }}Copy the code

After add this logic, the program run result becomes: Exception in the thread “is the main” Java. Lang. RuntimeException: singleton already exists! It is proved that the reflection destruction singleton is avoided.

5. Summary

This paper describes the definition, application scenarios and seven implementation methods of singleton pattern, and finally analyzes the reasons and solutions of two ways to destroy singleton pattern.

The advantage of the singleton pattern is that it can keep the uniqueness of objects in the system and save resources.

Based on the definition of the singleton pattern, its disadvantages are also obvious:

  • Singletons are too heavy and do not conform to the single responsibility principle
  • Lack of abstraction layer, difficult to extend

6. Reference materials

  • 5. Singleton Patterns — Graphic Design Patterns
  • How does reflection break the singleton pattern
  • The singleton pattern is implemented in Java using containers​

Finally, this article is included in the Personal Speaker Knowledge Base: Back-end technology as I understand it, welcome to visit.