[design mode] let's talk about the single example mode

? for some classes in the system, it is important to have only one instance. For example, a system can have multiple print tasks, but only one working task; a system can only have one window manager or file system; a system can only have one timing tool or ID (sequence number) generator.

How to ensure that a class has only one instance and that the instance is easy to access? Defining a global variable ensures that objects can be accessed at any time, but it does not prevent us from instantiating multiple objects. A better solution is to have the class itself responsible for keeping its only instance. This class can ensure that no other instance is created, and it can provide a method to access the instance. This is the motivation of the singleton model.

Singleton mode is an object creation mode, also known as singleton mode or singleton mode. The definition of Singleton Pattern is as follows: Singleton Pattern ensures that a class has only one instance, and it instantiates itself and provides the instance to the whole system. This class is called Singleton Pattern, which provides global access methods.

There are three points of singleton pattern: one is that a class can only have one instance; the other is that it must create the instance by itself; the third is that it must provide the instance to the whole system by itself.

The singleton mode includes the following roles:

  • Singleton: single example

? let's take a look at its implementation, because the singleton mode is the most important design mode. Let's talk about it carefully here:

Lazy writing:

public class LazySingleton {

    private static LazySingleton lazySingleton = null;

    private LazySingleton() {

    }

    public static LazySingleton getInstance() {
        if(lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

The key is to make the constructor private, and restrict the access to an instance only through internal static methods.

But this way of writing is obviously not thread safe. If more than one thread calls the getinstance method before the class is initialized, and the lazySingleton == null condition is correct, multiple LazySingleton instances will be generated in new. It can be changed as follows:

This is called DoubleCheck. When multiple threads enter the if(lazySingleton == null) code block before class initialization

At this time, add lock control and judge if(lazySingleton == null) again. If the condition is true, an instance will come out from new. When it's the turn of other threads to judge, it will naturally be false. The problem is roughly solved.

public class LazyDoubleCheckSingleton {

    private static LazyDoubleCheckSingleton lazySingleton = null;

    private LazyDoubleCheckSingleton() {

    }

    public static LazyDoubleCheckSingleton getInstance() {
        if(lazySingleton == null) {
            synchronized (LazyDoubleCheckSingleton.class){
                if(lazySingleton == null) {
                    lazySingleton = new LazyDoubleCheckSingleton();
                }
            }
        }
        return lazySingleton;
    }
}

But even so, some problems of the above code improvement cannot be solved.

Because there will be reordering problems. Reordering is a compilation optimization technology, which belongs to the content of compilation principles. We will not discuss it in detail here, but we will tell you what's going on.

Normally, the following code

lazySingleton = new LazyDoubleCheckSingleton();

The execution is as follows:
1. Allocate memory to this object
2. Initialization object
3. Set LazyDoubleCheckSingleton to point to the newly allocated memory address.

But after compiling and optimizing, it may be like this
1. Allocate memory to this object
3. Set LazyDoubleCheckSingleton to point to the newly allocated memory address.
2. Initialization object

Once step 2 and step 3 are reversed, something goes wrong. (precondition: compiler optimizes)
For example, there are two threads named thread 1 and thread 2. Thread 1 enters the if(lazySingleton == null) code block, obtains the lock, and executes new LazyDoubleCheckSingleton(). When loading the instance of the construction class, set LazyDoubleCheckSingleton to point to the newly allocated memory address, but the object has not been initialized. Thread 2 determines that if(lazySingleton == null) is false, and returns lazySingleton directly, and uses it again. When using it, there will be a problem.

Draw two pictures:

The reordering is as follows:

See the problem again

Of course, this is a good improvement. To disable reordering, add a volatile. If you are not familiar with thread safety, please refer to this article Detailed explanation of thread safety in Java Concurrent Programming

    private volatile static LazyDoubleCheckSingleton lazySingleton = null;

There are more than one method. You can also use the visibility of object initialization to solve this problem. Specifically, you can use the static internal class to delay loading based on class initialization. The name is very long, but it is not difficult to understand. (with this method, you don't need to worry about the problems caused by the above compilation and optimization.)

The delayed loading of class initialization is closely related to the JVM. In our example, the class is only loaded, but not linked or initialized.

Let's take a look at the implementation plan:
Define a static inner class whose static fields instantiate a singleton. To get a single instance, you need to call the getInstance method to get it indirectly.

public class StaticInnerClassSingleton {

    private static class InnerClass{
        private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
    }

    public static StaticInnerClassSingleton getInstance() {
        return InnerClass.staticInnerClassSingleton;
    }
}

If you are not familiar with inner classes, you can refer to this article [Java core technology volume] in depth understanding of Java internal classes


Here's the lazy introduction. Let's take a look at the implementation of another singleton mode

Hungry man

Show me the basic way

public class HungrySingleton {

    // Class initialization when loading
    private final static HungrySingleton hungrySingleton = new HungrySingleton();

    /*
    It can also be initialized in a static block
      private static HungrySingleton hungrySingleton´╝Ť

     static {
        hungrySingleton = new HungrySingleton();
     }
     */
    private HungrySingleton() {

    }

    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }

}

The instance of a single instance is instantiated when the class is loaded. If the class is not used, the memory resource will be wasted. Because the instance reference of a single instance is immutable, it is thread safe

In the same way, there are problems in the above starved Han style writing

Let's take a look at one by one:

First, the serialization destroys the singleton mode

To ensure that the starved Chinese can be serialized, you need to inherit the Serializable interface.

import java.io.Serializable;

public class HungrySingleton implements Serializable {

    // Class initialization when loading
    private final static HungrySingleton hungrySingleton = new HungrySingleton();

    /*
    It can also be initialized in a static block
      private static HungrySingleton hungrySingleton´╝Ť

     static {
        hungrySingleton = new HungrySingleton();
     }
     */
    private HungrySingleton() {

    }

    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }

}

Let's test:

package com.example.demo.example.count.singleton;

import lombok.extern.slf4j.Slf4j;

import java.io.*;

@Slf4j
public class Test {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
       HungrySingleton hungrySingleton = HungrySingleton.getInstance();
       ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton"));
       oos.writeObject(hungrySingleton);

       File file = new File("singleton");
       ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));

        HungrySingleton newHungrySingleton = (HungrySingleton) ois.readObject();

        log.info("Result {}",hungrySingleton);
        log.info("Result {}",newHungrySingleton);
        log.info("Contrast result {}",hungrySingleton == newHungrySingleton);
    }
}

Result:

The results show that the objects are different, and the underlying cause of serialization is involved. Let's look at the solution first:

Add the following code to the starved Chinese code

private Object readResolve() {
        return hungrySingleton;
    }

Re run, the result at this time:

The reason lies in the readResolve method. Next, go to the ObjectInputStream source code section to find the reason. (it's all about bottom level implementation, don't expect to understand)

There is a description on a method of reading the underlying data
It is possible that there is a readResolve method defined in the serialized Object class. We see the judgment in the binary data reading method

This code is in the private Object readOrdinaryObject() method. If there is a ReadResolve method, call it. Does not exist, does not call. If you think of the code we added in starving Chinese style, you can roughly guess what happened.

Another case is the single case of reflection attack damage

Demonstrate

package com.example.demo.example.count.singleton;

import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

@Slf4j
public class Test {

    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class objectClass = HungrySingleton.class;

        Constructor constructor = objectClass.getDeclaredConstructor();
        constructor.setAccessible(true); // Force open constructor permission
        HungrySingleton instance = HungrySingleton.getInstance();
        HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();

        log.info("Result{}",instance);
        log.info("Result{}",newInstance);
        log.info("Comparison results{}",newInstance == instance);
    }
}


Here, we break the permission of private construction method so that a single instance can be generated by new. This is not what we want to see.

The solution is to throw an exception in the constructor

   private HungrySingleton() {
        if( hungrySingleton != null) {
            throw new RuntimeException("The singleton constructor forbids reflection calls");
        }
    }

Run it again at this time

In fact, for lazy type, there is also a single case of reflection violation, which can be solved by similar methods of throwing exceptions.

A comparison between the single case of hungry and the single case of lazy

  • The starved Chinese singleton class instantiates itself when it is loaded. In terms of resource utilization efficiency, this is slightly worse than the lazy singleton. From the point of view of speed and reaction time, it is slightly better than the lazy type.
  • When the lazy singleton class is instantiated, it must deal with the access restriction problem when multiple threads refer to this class for the first time at the same time, especially when the singleton class is instantiated as a resource controller, it must involve resource initialization, which is likely to take a lot of time, which means that the probability of multiple threads referring to this class for the first time at the same time becomes larger, and it needs to be synchronized Control by mechanism.

? look at the advantages and disadvantages of a single mode:
Advantages of singleton mode

  • Provides controlled access to a unique instance. Because a singleton class encapsulates its unique instance, it has strict control over how and when customers access it, and provides a shared concept for design and development teams.
  • Because there is only one object in the system memory, it can save system resources. For some objects that need to be created and destroyed frequently, the singleton mode can undoubtedly improve the performance of the system.
  • Allow variable destination instances. We can extend it based on the singleton mode, using a method similar to singleton control to obtain a specified number of object instances

Disadvantages of singleton mode

  • Because there is no abstract layer in singleton pattern, it is very difficult to extend singleton class.
  • To some extent, the single responsibility principle is violated. Because the singleton class not only acts as the factory role, provides the factory method, but also acts as the production role, including some business methods, which integrates the creation of the product with the function of the product itself.
  • Abuse of single instance will bring some negative problems. For example, in order to save resources, designing database connection pool objects as single instance classes may lead to overflow of connection pool due to too many programs sharing connection pool objects. Nowadays, many object-oriented languages (such as Java and C) run environments provide automatic garbage collection technology. Therefore, if the instantiated objects are not used for a long time , the system will think it is garbage, and will automatically destroy and recycle resources. The next time it is used, it will be re instantiated, which will lead to the loss of object state.

? the singleton mode can be used in the following cases:

  • The system only needs one instance object. For example, the system requires a unique serial number generator, or it needs to consider that the resource consumption is too large and only one object can be created.
  • Only one public access point is allowed to be used for a single instance of the customer calling class. Except for the public access point, the instance cannot be accessed through other ways.
  • In a system, when a class requires only one instance, the singleton pattern should be used. Conversely, if a class can have several instances coexisting, it is necessary to improve the singleton pattern to make it a multiple instance pattern.

Keywords: Java Lombok Programming jvm

Added by barry_p on Wed, 13 Nov 2019 05:14:02 +0200