Elaborate on 23 design patterns-006: deeply study the underlying implementation principle of single case

1. Deeply study the curriculum arrangement of the underlying implementation principle of single case

Course content

  1. Basic design ideas and concepts of singleton pattern
  2. Application scenario of singleton mode
  3. Multiple ways to write single case mode
  4. Why do lazy people need double check locks
  5. How do you create objects in Java
  6. How to prevent a single instance from being reflected, serialized and cracked
  7. The underlying implementation principle of creating objects by deserialization

2. Basic concepts, advantages and disadvantages of singleton mode

Basic concept of single case
There will only be one instance in the Jvm

Advantages and disadvantages of single case
advantage:

  1. Objects will not be created frequently to save server memory;
  2. There is only one instance to realize reuse, there is no need to create objects, and the access speed is relatively fast;

Disadvantages:

  1. Because the object has only one instance, when multiple threads share the same singleton object at the same time, there may be thread safety problems;
  2. Singleton objects are generally created and stored in the persistent area with static. They will not be recycled by the garbage collection mechanism and will always occupy the server memory. If they are created too much, the server memory may overflow;

Single case application scenario

  1. The configuration files defined in the project are all single instances;
  2. Spring IOC container creates object default singleton (reuse);
  3. Thread pool and database connection pool;
  4. The Servlet object defaults to a singleton, which is thread unsafe, and spring MVC encapsulation based on Servlet is also unsafe;
  5. The Jvm has a built-in caching framework. The bottom layer is based on HashMap encapsulation + elimination strategy, and the HashMap object is a single instance;
  6. Enumerations, constants
  7. Site counter

3 handwriting singleton mode

Lazy concept: the object is created only when it is really needed.
Note: you need to privatize the constructor

Handwritten lazy (thread unsafe)

public class Singleton01 {

    private static Singleton01 singleton01 = null;

    /**
     * Privatization
     */
    private Singleton01() {
        System.out.println("singleton01");
    }

    public static Singleton01 getInstance() {
        if (singleton01 == null) {
            singleton01 = new Singleton01();
        }
        return singleton01;
    }

    public static void main(String[] args) {
        Singleton01 instance1 = Singleton01.getInstance();
        Singleton01 instance2 = Singleton01.getInstance();
        System.out.println(instance1 == instance2);
    }
}

Operation results:

This writing method: if multiple threads access at the same time, it may cause thread insecurity, create multiple objects, and violate the single instance uniqueness principle.
Thread safety problem: multiple threads share the same global variable at the same time. Thread safety problems may occur when writing.

Handwritten lazy (thread safe)

public class Singleton02 {

    private static Singleton02 singleton02;
    
    private Singleton02(){
        
    }

    // When synchronized is added to the method, it will also be locked when reading, which is inefficient
    public static synchronized Singleton02 getInstance() {
        if (singleton02 == null) {
            singleton02 = new Singleton02();
        }
        return singleton02;
    }

    public static void main(String[] args) {
        Singleton02 instance1 = Singleton02.getInstance();
        Singleton02 instance2 = Singleton02.getInstance();
        System.out.println(instance1 == instance2); // true
    }
}

This writing method: although it can ensure thread safety, as long as the object is created, the lock needs to be obtained when obtaining the object later, which is very inefficient.

Lazy double check lock

public class Singleton03 {
    private static Singleton03 singleton03;

    private Singleton03() {
    }

    public static Singleton03 getInstance() {
        // The first judgment of locking is to ensure that the object will be locked only when it is created, and the acquired object will not be locked
        if (singleton03 == null) {
            synchronized (Singleton03.class) {
                // The second judgment ensures that the following code processes a logical single thread to prevent the creation of multiple objects
                if (singleton03 == null) {
                    singleton03 = new Singleton03();
                }
            }

        }
        return singleton03;
    }

    public static void main(String[] args) {
        Singleton03 instance1 = Singleton03.getInstance();
        Singleton03 instance2 = Singleton03.getInstance();
        System.out.println(instance1 == instance2);
    }
}

This writing method: it can not only solve the thread safety problem (two judgments ensure that the created object is unique), but also has high efficiency (the lock will not be entered when obtaining the object)

The difference between lazy and hungry
Lazy: the object will be created only when it is really needed, which relatively saves memory space. Disadvantages: the thread is inherently unsafe, so it is necessary to ensure thread safety through code in the later stage;
Hungry Chinese style: the object will be created when the class file is loaded. It is inherently thread safe (only loaded once). The disadvantage is that it wastes memory space.

Hungry Han implementation singleton (thread safety)

public class Singleton04 {

    // This object is created when the class file is loaded
//    private static Singleton04 singleton04 = new Singleton04();
    // Create constant writing
    public static final Singleton04 singleton04 = new Singleton04();

    private Singleton04() {
        System.out.println("singleton04");
    }

    public static Singleton04 getInstance() {
        return singleton04;
    }

    public static void main(String[] args) {
//        Singleton04 instance1 = Singleton04.getInstance();
//        Singleton04 instance2 = Singleton04.getInstance();
//        System.out.println(instance1 == instance2);
        System.out.println(Singleton04.getInstance() == Singleton04.getInstance());
    }
}

Constants defined in the project are in the form of hungry Chinese. If too many constants are defined, it may lead to memory overflow.

Implementation of singleton based on static code block

public class Singleton05 {

    private static Singleton05 singleton05 = null;

    private Singleton05() {
    }

    static {
        System.out.println("current class Loaded");
        singleton05 = new Singleton05();
    }

    public static Singleton05 getInstance() {
        return singleton05;
    }

    public static void main(String[] args) {
//        Singleton05 instance1 = Singleton05.getInstance();
//        Singleton05 instance2 = Singleton05.getInstance();
//        System.out.println(instance1 == instance2);
    }
}

In hungry Chinese style, class is initialized when it is loaded.

4 how to use reflection mechanism to crack single cases & how to defend

How can objects be created in the Jvm?
Reflection mechanism, serialization, new, cloning technology

public class Test001 {
    public static void main(String[] args) throws Exception {
        Singleton01 instance1 = Singleton01.getInstance();
        // Create objects using the reflection mechanism
        Class<?> aClass = Class.forName("com.mayikt.Singleton01");
//        Constructor<?>  constructor = aClass.getConstructor(); //  Get all constructors, including those of the parent class Object, and report an error
        Constructor<?> constructor = aClass.getDeclaredConstructor();// Gets the parameterless constructor of the current class, excluding the parent class
        constructor.setAccessible(true);
        Singleton01 instance2 = (Singleton01) constructor.newInstance();
        System.out.println(instance1 == instance2);
    }
}

Operation results:

How to defend against single instance of reflection cracking

public class Singleton01 {

    private static Singleton01 singleton01 = null;

    /**
     * Privatization
     */
    private Singleton01() throws Exception {
        if (singleton01 != null) {
            throw new Exception("This object has already been created and cannot be created repeatedly");
        } else {
            singleton01 = this;
        }
        System.out.println("singleton01");
    }

    public static Singleton01 getInstance() throws Exception {
        if (singleton01 == null) {
            singleton01 = new Singleton01();
        }
        return singleton01;
    }

    public static void main(String[] args) throws Exception {
        Singleton01 instance1 = Singleton01.getInstance();
        Singleton01 instance2 = Singleton01.getInstance();
        System.out.println(instance1 == instance2);
    }
}

Operation results:

If there is no else part, when the reflection creation object is called before getInstance() creates the object, the defense is invalid.

5 use serialization to destroy singleton & how to defend

Serialization: convert the object into binary form and store it directly on the hard disk for persistence;
Deserialization: read binary files from local files and convert them into objects;

public class Test003 {
    public static void main(String[] args) throws Exception {
        // 1. The object needs to be serialized to local storage
        FileOutputStream fos = new FileOutputStream("d:/code/singleton.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        Singleton01 instance1 = new Singleton01();
        oos.writeObject(instance1);
        oos.close();
        fos.close();
        //2. Deserialize objects from hard disk to memory
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/code/singleton.txt"));
        Singleton01 instance2 = (Singleton01) ois.readObject();
        System.out.println(instance1 == instance2);
    }
}

Operation results:

Serialization creates objects that violate the singleton design principle

doubt:

  1. Why does serialization create a new object without a parameterless constructor?
  2. How to create a new object

How to create a new object

public class Singleton01 implements Serializable {

    private static Singleton01 singleton01 = null;

    /**
     * Privatization
     */
    Singleton01() throws Exception {
        if (singleton01 != null) {
            throw new Exception("This object has already been created and cannot be created repeatedly");
        } else {
            singleton01 = this;
        }
        System.out.println("singleton01");
    }

    public static Singleton01 getInstance() throws Exception {
        if (singleton01 == null) {
            singleton01 = new Singleton01();
        }
        return singleton01;
    }

    public static void main(String[] args) throws Exception {
        Singleton01 instance1 = Singleton01.getInstance();
        Singleton01 instance2 = Singleton01.getInstance();
        System.out.println(instance1 == instance2);
    }

    /**
     * The serial number generation callback method is used to deserialize and generate a singleton object
     * The method name must be fixed write dead readResolve
     * @return
     */
    public Object readResolve() {
        return singleton01;
    }
}

Operation results:

6 serialization how to generate a new object

How does serialization generate a new object
Creating an Object in the form of serialization does not go through the serialization class parameterless constructor. Judge whether the serialization class implements the Serializable interface. If the interface is implemented, transfer the parameterless constructor of the class's parent class (which does not implement the Serializable interface) to instantiate the Object through the parent class. If the parent class also implements the Serializable interface, initialize the Object through the Object class.

public class Singleton01 extends UserEntity implements Serializable {

    private static Singleton01 singleton01 = null;

    /**
     * Privatization
     */
    Singleton01() throws Exception {
        if (singleton01 != null) {
            throw new Exception("This object has already been created and cannot be created repeatedly");
        } else {
            singleton01 = this;
        }
        System.out.println("singleton01");
    }

    public static Singleton01 getInstance() throws Exception {
        if (singleton01 == null) {
            singleton01 = new Singleton01();
        }
        return singleton01;
    }

    public static void main(String[] args) throws Exception {
        Singleton01 instance1 = Singleton01.getInstance();
        Singleton01 instance2 = Singleton01.getInstance();
        System.out.println(instance1 == instance2);
    }

    /**
     * The serial number generation callback method is used to deserialize and generate a singleton object
     * The method name must be fixed write dead readResolve
     * @return
     */
    public Object readResolve() {
        return singleton01;
    }
}
public class UserEntity extends Object implements Serializable {
    public UserEntity() {
        System.out.println("Parent class initialization started");
    }
}

Operation results:

How the readResolve method executes
When serialization creates an object, judge whether there is a readResolve method in the serialization class. If so, directly call the readResolve method in the serialization class to return a singleton object.

7 enumeration is the safest singleton

Serialization and reflection cracking can not be solved at the bottom
Enumerate the safest singletons:

  1. Congenital default guarantee singleton for objects generated by serialization
  2. Reflection failed to initialize to enumeration
public enum Singleton06 {

    INSTANCE;

    public void addUser(){
        System.out.println("Enumeration class, congenital security");
    }

    Singleton06(){
        System.out.println(">>Parameterless constructor execution");
    }
}
public class Test004 {
    public static void main(String[] args) throws Exception {
//        Singleton06 instance1 = Singleton06.INSTANCE;
//        Singleton06 instance2 = Singleton06.INSTANCE;
//        System.out.println(instance1 == instance2);
//
//        Class<?> aClass = Class.forName("com.mayikt.Singleton06");
//        aClass.newInstance();  //  report errors

        // 1. The object needs to be serialized to local storage
        FileOutputStream fos = new FileOutputStream("d:/code/enum.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        Singleton06 instance1 = Singleton06.INSTANCE;
        oos.writeObject(instance1);
        oos.close();
        fos.close();
        //2. Deserialize objects from hard disk to memory
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/code/enum.txt"));
        Singleton06 instance2 = (Singleton06) ois.readObject();
        System.out.println(instance1 == instance2);
    }
}

Operation results:

Source code download address (mayikt_designPattern_6.zip):
Link: https://pan.baidu.com/s/1wWKZN1MbXICZVW1Vxtwe6A
Extraction code: fire

Tags: Java Singleton pattern

Posted on Sun, 10 Oct 2021 19:26:57 -0400 by geus