Single case mode can also play flowers

Time:2022-1-4

1、 Singleton mode

1. What is singleton mode

(1) Singleton mode

[singleton pattern:]
definition:
    Ensure a class has only one instance, and provide a global point of access to it.
    Ensure that a class has only one instance and provide a global access point to it (only instance objects are allowed to be obtained through the global access point).

 

(2) Implementation points of singleton mode

In general, to access a variable or method in a class:
    You can instantiate an object through new, and then obtain it in the form of "object name. Variable name" and "object name. Method name".
    You can modify (global) variables and methods through static, and then obtain them in the form of "class name. Variable name" and "class name. Method name". You can avoid using new for object instantiation.

To ensure that there is only one instance of a class, you should ensure that it has and only has one instance:
    You should ensure that its constructor cannot be called outside the class (prevent object instantiation with new).
    Constructor can only be called once in this class.
Note:
    Common methods of object instantiation: new, serialization, cloning and reflection.
    
Basic implementation points:
    Privatization of construction methods (preventing object instantiation using new).
    Instantiate once inside the class (constructor can only be called once in the class).
    Provide a global access point (global variable, global method, etc.) externally. You can obtain the instance object in the form of "class name. Variable name" or "class name. Method name" (avoid using new for object instantiation).
Note:
    Reflection will destroy the privatization of construction methods. It should be noted that it will be introduced later.
    Serialization, cloning and other operations may break the singleton mode. Need attention.

 

(3) Usage scenario
When an object is frequently created and destroyed, the singleton mode can be considered.
When creating an object consumes too much resources, but it is often used, you can consider the singleton mode.

 

2. Implementation of common singleton mode

(1) Implementation mode

[hungry Han style:]
    Static variable
    Static code block
    Enumeration (recommended)

[lazy style:]
    Static method
    Synchronized synchronization method
    Synchronized code block
    duplication check
    Static inner class

 

(2) The difference between hungry and lazy

[basic difference:]
    Lazy type instantiates objects only when they need to be used.
    The instantiation operation is completed when the class is loaded, and the object may not be used for the time being (occupying memory).

[hungry Han style:]
Core:
    Hungry Han completes the instantiation operation in the initialization stage of class loading with the help of the class loading mechanism of JVM.
    The class initialization phase is executed only once to ensure the uniqueness of the instance and thread safety.
    Class initialization occurs only when the class is actively used. Passive use will not lead to class initialization.
How to actively use classes:
    When the main method of class is called.
    When performing the new instantiation operation.
    When accessing static variables and static methods.
    When instantiating a subclass (trigger parent class initialization first).
    Reflection calls a class.
For JVM class loading process, refer to: https://www.cnblogs.com/l-y-h/p/13496969.html#_label1_5

[lazy style:]
Core:
    Instantiation is performed only when objects need to be used.
    In a multithreaded environment, multiple threads may use objects at the same time. Thread safety needs to be considered to prevent concurrent access from generating multiple instances.

 

2、 Hungry Han style

1. Realize

(1) Basic description

[core idea:]
    Use the static keyword to initialize the instance with the help of the class loading process.
    Use private to modify the construction method to ensure the privatization of the construction method.
    Provide a global access point (class name. Variable name or class name. Method name) to obtain objects.    
 
[available methods:]
    Static variable
    Static method
    Static code block

[advantages:]
    In the initialization phase of class loading, instantiation is completed and loaded only once. Ensure object uniqueness and thread safety.
    
[disadvantages:]
    Instantiation is completed in the initialization phase of class loading, and lazy loading is not implemented, which may cause a waste of memory (created when it is not needed).

 

(2) Code implementation (static variables)
Public modifies a variable and obtains the object directly through “class name. Variable name”.

class HungrySingleton {
    //Provide a global access point, which can be accessed through "class name. Variable name"
    public static HungrySingleton singleton = new HungrySingleton();

    //Constructor Privatization (prevent instance objects from being created through new)
    private HungrySingleton() {
    }
}

public class Test {
    public static void main(String[] args) {
        HungrySingleton singleton = HungrySingleton.singleton;
        HungrySingleton singleton2 = HungrySingleton.singleton;
        System. out. println(singleton == singleton2);  //  True for the same object
    }
}

 

(3) Code implementation (static method)
Private modifies a variable and is not allowed to be accessed in the form of “class name. Variable name”.
Public modifies the method to obtain objects by means of “class name. Method name”.

class HungrySingleton {
    //Privatized variables cannot be accessed in the form of "class name. Variable name"
    private static HungrySingleton singleton = new HungrySingleton();

    //Constructor Privatization (prevent instance objects from being created through new)
    private HungrySingleton() {
    }

    //Provide a global access point, which can be accessed through "class name. Variable name"
    public static HungrySingleton getInstance() {
        return singleton;
    }
}

public class Test {
    public static void main(String[] args) {
        HungrySingleton singleton = HungrySingleton.getInstance();
        HungrySingleton singleton2 = HungrySingleton.getInstance();
        System. out. println(singleton == singleton2);  //  True for the same object
    }
}

 

(4) Code implementation (static code block)
Static code block, which only moves the instantiation operation to the static code block for implementation.

class HungrySingleton {
    //Privatized variables cannot be accessed in the form of "class name. Variable name"
    private static HungrySingleton singleton;

    //Instantiation in the static code block is also executed in the class loading initialization phase
    static {
        singleton = new HungrySingleton();
    }

    //Constructor Privatization (prevent instance objects from being created through new)
    private HungrySingleton() {
    }

    //Provide a global access point, which can be accessed through "class name. Variable name"
    public static HungrySingleton getInstance() {
        return singleton;
    }
}

public class Test {
    public static void main(String[] args) {
        HungrySingleton singleton = HungrySingleton.getInstance();
        HungrySingleton singleton2 = HungrySingleton.getInstance();
        System. out. println(singleton == singleton2);  //  True for the same object
    }
}

 

(5) Is that it?
Of course not. This just prevents instantiating objects through new.
Object instantiation includes reflection, serialization, cloning and other operations.
Will these operations break the singleton mode? You need to think about it.

 

2. Reflection failure

(1) Class is initialized only when it is actively used
Class is initialized only when it is actively used. The class is not necessarily used, and the initialization operation will be triggered.
For example:
The class loading process is not triggered when the private constructor is obtained by reflection.
After the following code is executed, “start…” in the static code block No output.

import java.lang.reflect.Constructor;

class HungrySingleton {
    //Privatized variables cannot be accessed in the form of "class name. Variable name"
    private static HungrySingleton singleton;

    //Instantiation in the static code block is also executed in the class loading initialization phase
    static {
        System.out.println("start...");
        singleton = new HungrySingleton();
    }

    //Constructor Privatization (prevent instance objects from being created through new)
    private HungrySingleton() {
    }

    //Provide a global access point, which can be accessed through "class name. Variable name"
    public static HungrySingleton getInstance() {
        return singleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Class hungrySingletonClass = HungrySingleton.class;
        Constructor hungrySingletonConstructor = hungrySingletonClass.getDeclaredConstructor();
        hungrySingletonConstructor.setAccessible(true);
    }
}

 

(2) Reflection failure
As shown in the following code, when the reflection calls the construction method, the class loading process will be carried out (output “start…”), Then build an instance.
The instance object at this time is the object recreated by the construction method. Unlike objects created during class loading.
That is, reflection destroys the singleton mode.

import java.lang.reflect.Constructor;

class HungrySingleton {
    //Privatized variables cannot be accessed in the form of "class name. Variable name"
    private static HungrySingleton singleton;

    //Instantiation in the static code block is also executed in the class loading initialization phase
    static {
        System.out.println("start...");
        singleton = new HungrySingleton();
    }

    //Constructor Privatization (prevent instance objects from being created through new)
    private HungrySingleton() {
    }

    //Provide a global access point, which can be accessed through "class name. Variable name"
    public static HungrySingleton getInstance() {
        return singleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Class hungrySingletonClass = HungrySingleton.class;
        Constructor hungrySingletonConstructor = hungrySingletonClass.getDeclaredConstructor();
        hungrySingletonConstructor. setAccessible(true); //  At this point, the class loading process is not triggered
        HungrySingleton singleton = hungrySingletonConstructor. newInstance(); //  At this point, the class loading process is triggered and an instance is created
        HungrySingleton singleton2 = HungrySingleton.getInstance();
        System. out. println(singleton == singleton2);  //  False, not the same object
     }
}

 

(3) Prevent reflection damage (not necessarily effective)
In the construction method, judge whether the instance has been created.
During class initialization, an instance is created. Even if the construction method is called through reflection, it will be called after the instance is created. Therefore, if it is judged in the construction method that the instance exists, an exception will be thrown. This prevents reflection corruption (not necessarily effective, as mentioned in subsequent serialization corruption).

import java.lang.reflect.Constructor;

class HungrySingleton {
    //Privatized variables cannot be accessed in the form of "class name. Variable name"
    private static HungrySingleton singleton;

    //Instantiation in the static code block is also executed in the class loading initialization phase
    static {
        System.out.println("start...");
        singleton = new HungrySingleton();
    }

    //Constructor Privatization (prevent instance objects from being created through new)
    private HungrySingleton() {
        if (singleton != null) {
            Throw new runtimeException ("instance already exists, duplicate creation is not allowed");
        }
    }

    //Provide a global access point, which can be accessed through "class name. Variable name"
    public static HungrySingleton getInstance() {
        return singleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Class hungrySingletonClass = HungrySingleton.class;
        Constructor hungrySingletonConstructor = hungrySingletonClass.getDeclaredConstructor();
        hungrySingletonConstructor.setAccessible(true);
        HungrySingleton singleton = hungrySingletonConstructor.newInstance();
        HungrySingleton singleton2 = HungrySingleton.getInstance();
        System. out. println(singleton == singleton2);  //  True for the same object
    }
}

 

3. Deserialization violation

(1) Serialization and deserialization
When you serialize an object and read it again (deserialization), a new object is created.
Note:
Serialization is to write the entity object state into an ordered byte stream according to a certain format.
Deserialization is to reconstruct an object from an ordered byte stream and restore the object state.

[deserialization core code:]
Readordinaryobject() method in objectinputstream

private Object readOrdinaryObject(boolean unshared) throws IOException {
    Object obj;
    try {
        obj = desc.isInstantiable() ? desc.newInstance() : null;
    } catch (Exception ex) {
        throw (IOException) new InvalidClassException(desc.forClass().getName(), "unable to create instance").initCause(ex);
    }

    if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {
        Object rep = desc.invokeReadResolve(obj);
        if (rep != obj) {
            handles.setObject(passHandle, obj = rep);
        }
    }
    return obj;
}

[focus 1: (call constructor instantiation)]
desc.isInstantiable()
    If a serializable / externalizable class can be instantiated at run time, the method returns true.

desc.newInstance()
    Create an object by calling a parameterless construct through reflection.
Note:
    The parameterless construction called here is different from the parameterless construction method of the class itself.
    From the actual effect, only class loading is triggered here, and the constructor of the class is not triggered. It is different from the reflection mentioned earlier.
    There is no in-depth study. If you are interested, you can help answer it.

[focus 2: (user defined object generation strategy)]
desc.hasReadResolveMethod()
    Returns true if the class of a serializable / externalizable interface contains the readresolve () method.

desc.invokeReadResolve(obj)
    Call the readresolve () method of the class to be deserialized through reflection.

handles.setObject(passHandle, obj = rep)
    If the instance returned by readresolve () is different from that created by the constructor method, the instance created by readresolve () method shall prevail.

 

(2) Deserialization violation
As shown in the following code, an object is created by deserialization.
From the actual code execution results, deserialization only triggers the class loading process (the constructor is called at this time), and newinstance() in deserialization does not actively trigger the constructor of the class, so the judgment in the construction method here cannot prevent the rows reflected in deserialization.
That is, deserialization destroys the singleton pattern.

import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;

class HungrySingleton implements Serializable {
    private static final long serialVersionUID = 42L;

    //Privatized variables cannot be accessed in the form of "class name. Variable name"
    private static HungrySingleton singleton;

    static {
        System.out.println("start...");  // start...
        singleton = new HungrySingleton();
    }

    //Constructor Privatization (prevent instance objects from being created through new)
    private HungrySingleton() {
        if (singleton != null) {
            Throw new runtimeException ("instance already exists, duplicate creation is not allowed");
        }
    }

    //Provide a global access point, which can be accessed through "class name. Variable name"
    public static HungrySingleton getInstance() {
        return singleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
//        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
//        oos.writeObject(HungrySingleton.getInstance());

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"));
        HungrySingleton singleton = (HungrySingleton) ois.readObject();

        HungrySingleton singleton2 = HungrySingleton.getInstance();
        System. out. println(singleton == singleton2); //  False, not the same object
    }
}

 

(3) Prevent deserialization corruption
An instance object can be returned through readresolve() to ensure that it is an instance object created during class loading, so as to prevent deserialization destruction.

import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;

class HungrySingleton implements Serializable {
    private static final long serialVersionUID = 42L;

    //Privatized variables cannot be accessed in the form of "class name. Variable name"
    private static HungrySingleton singleton;

    static {
        System.out.println("start...");  // start...
        singleton = new HungrySingleton();
    }

    //Constructor Privatization (prevent instance objects from being created through new)
    private HungrySingleton() {
        if (singleton != null) {
            Throw new runtimeException ("instance already exists, duplicate creation is not allowed");
        }
    }

    //Provide a global access point, which can be accessed through "class name. Variable name"
    public static HungrySingleton getInstance() {
        return singleton;
    }

    //Define the readresolve () method to return the instance object created during class loading (this object is returned during deserialization)
    private Object readResolve() {
        return singleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
//        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
//        oos.writeObject(HungrySingleton.getInstance());

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"));
        HungrySingleton singleton = (HungrySingleton) ois.readObject();

        HungrySingleton singleton2 = HungrySingleton.getInstance();
        System. out. println(singleton == singleton2); //  True for the same object
    }
}

 

4. Clone destruction

(1) Clone destruction
As shown in the following code, an object is created by cloning.
The clone method (native method) of the object is called, which is similar to deserialization and does not trigger the construction method of the class (it should be copied directly from memory). Created a new object.
That is, cloning destroys the singleton mode.

class HungrySingleton implements Cloneable {
    //Privatized variables cannot be accessed in the form of "class name. Variable name"
    private static HungrySingleton singleton;

    static {
        System.out.println("start...");  // start...
        singleton = new HungrySingleton();
    }

    //Constructor Privatization (prevent instance objects from being created through new)
    private HungrySingleton() {
        if (singleton != null) {
            Throw new runtimeException ("instance already exists, duplicate creation is not allowed");
        }
    }

    //Provide a global access point, which can be accessed through "class name. Variable name"
    public static HungrySingleton getInstance() {
        return singleton;
    }

    //Override the clone () method to return the clone object
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        HungrySingleton singleton = HungrySingleton.getInstance();
        HungrySingleton singleton2 = (HungrySingleton) singleton.clone();
        System. out. println(singleton == singleton2); //  False, not the same object
    }
}

 

(2) Prevent clone corruption
Ensure that the object returned by the clone () method is the instance object created during class loading to prevent clone corruption.

class HungrySingleton implements Cloneable {
    //Privatized variables cannot be accessed in the form of "class name. Variable name"
    private static HungrySingleton singleton;

    static {
        System.out.println("start...");  // start...
        singleton = new HungrySingleton();
    }

    //Constructor Privatization (prevent instance objects from being created through new)
    private HungrySingleton() {
        if (singleton != null) {
            Throw new runtimeException ("instance already exists, duplicate creation is not allowed");
        }
    }

    //Provide a global access point, which can be accessed through "class name. Variable name"
    public static HungrySingleton getInstance() {
        return singleton;
    }

    //Override the clone () method to return the clone object
    @Override
    public Object clone() throws CloneNotSupportedException {
        return singleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        HungrySingleton singleton = HungrySingleton.getInstance();
        HungrySingleton singleton2 = (HungrySingleton) singleton.clone();
        System. out. println(singleton == singleton2); //  True is the same object
    }
}

 

5. Enumeration

(1) Basic description

[Basic Description:]
    Write a simple enum class and decompile javap - C XX class。
    You can see that the bottom layer is similar to the writing of starving Chinese static code blocks. The instantiation operation is completed in the initialization phase of class loading.

[advantages:]
    In the initialization phase of class loading, instantiation is completed and loaded only once. Ensure object uniqueness and thread safety.
    It can prevent cloning, deserialization and reflection from destroying the singleton mode.
    The writing method is simple.

 

(2) Decompile the enum class

【EnumSingleton】
enum EnumSingleton {
    INSTANCE;
}

【javap -c EnumSingleton.class】
final class pattern.sington.EnumSingleton extends java.lang.Enum {
  public static final pattern.sington.EnumSingleton INSTANCE;

  public static pattern.sington.EnumSingleton[] values();
    Code:
       0: getstatic     #1                  // Field $VALUES:[Lpattern/sington/EnumSingleton;
       3: invokevirtual #2                  // Method "[Lpattern/sington/EnumSingleton;".clone:()Ljava/lang/Object;
       6: checkcast     #3                  // class "[Lpattern/sington/EnumSingleton;"
       9: areturn

  public static pattern.sington.EnumSingleton valueOf(java.lang.String);
    Code:
       0: ldc           #4                  // class pattern/sington/EnumSingleton
       2: aload_0
       3: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
       6: checkcast     #4                  // class pattern/sington/EnumSingleton
       9: areturn

  static {};
    Code:
       0: new           #4                  // class pattern/sington/EnumSingleton
       3: dup
       4: ldc           #7                  // String INSTANCE
       6: iconst_0
       7: invokespecial #8                  // Method "":(Ljava/lang/String;I)V
      10: putstatic     #9                  // Field INSTANCE:Lpattern/sington/EnumSingleton;
      13: iconst_1
      14: anewarray     #4                  // class pattern/sington/EnumSingleton
      17: dup
      18: iconst_0
      19: getstatic     #9                  // Field INSTANCE:Lpattern/sington/EnumSingleton;
      22: aastore
      23: putstatic     #1                  // Field $VALUES:[Lpattern/sington/EnumSingleton;
      26: return
}

[equivalent to:]
public final class EnumSingleton extends Enum< EnumSingleton> {
    public static final EnumSingleton INSTANCE;
    public static EnumSingleton[] values();
    public static EnumSingleton valueOf(String s);
    static {
        INSTANCE = new EnumSingleton(name, ordinal);
    };
}

 

(3) Prevent reflection damage
Class of enumeration type, no parameterless construction. It inherits the parameterized construction of enum by default.

[code implementation:]
import java.lang.reflect.Constructor;

enum EnumSingleton {
    INSTANCE;
}

public class Test {
    public static void main(String[] args) throws Exception {
        Class enumSingletonClass = EnumSingleton.class;
        Constructor enumSingletonConstructor = enumSingletonClass.getDeclaredConstructor(String.class, int.class);
        enumSingletonConstructor.setAccessible(true);
        //An exception occurs in newinstance, Java lang.IllegalArgumentException: Cannot reflectively create enum objects
        EnumSingleton enumSingleton = enumSingletonConstructor.newInstance();  

        EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE;

        System.out.println(enumSingleton == enumSingleton2);
    }
}

[cause analysis:]
Judge in the newinstance() method. If it is an enumeration type, throw an exception.

@CallerSensitive
public T newInstance(Object ... initargs) throws IllegalArgumentException
{
    if ((clazz.getModifiers() & Modifier.ENUM) != 0)
        throw new IllegalArgumentException("Cannot reflectively create enum objects");
}

 

(4) Prevent clone corruption
Cannot override clone() method for enumerating classes of type. The clone () method defined in its parent class enum is of type final and cannot be overridden by subclasses.

protected final Object clone() throws CloneNotSupportedException {
    throw new CloneNotSupportedException();
}

 

(5) Prevent serialization corruption
Serialization returns the same object without defining the readresolve () method. It performs another logic.

[code implementation:]
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

enum EnumSingleton {
    INSTANCE;
}

public class Test {
    public static void main(String[] args) throws Exception {
        EnumSingleton enumSingleton = EnumSingleton.INSTANCE;

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
        oos.writeObject(enumSingleton);

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"));
        EnumSingleton enumSingleton2 = (EnumSingleton) ois.readObject();

        System. out. println(enumSingleton == enumSingleton2); //  True is the same object
    }
}

[deserialization core code:]
Readenum() method in objectinputstream.
Reads in and returns an enumeration constant. If the enumeration type is not resolvable, null is returned.

private Enum> readEnum(boolean unshared) throws IOException {

    ObjectStreamClass desc = readClassDesc(false);

    int enumHandle = handles.assign(unshared ? unsharedMarker : null);
    
    String name = readString(false);
    Enum> result = null;
    Class> cl = desc.forClass();
    if (cl != null) {
        try {
            @SuppressWarnings("unchecked")
            Enum> en = Enum.valueOf((Class)cl, name);
            result = en;
        } catch (IllegalArgumentException ex) {
            throw (IOException) new InvalidObjectException(
                "enum constant " + name + " does not exist in " +
                cl).initCause(ex);
        }
        if (!unshared) {
            handles.setObject(enumHandle, result);
        }
    }

    handles.finish(enumHandle);
    passHandle = enumHandle;
    return result;
}

 

3、 Lazy style

1. Realize

(1) Basic description

[core idea:]
    Use private to modify the construction method to ensure the privatization of the construction method.
    A static public method is provided, and the instance object is created only when the method is called. (global access point, get the object through "class name. Method name").    
 
[available methods:]
    Static method
    Synchronized synchronization method
    Synchronized code block
    duplication check
    Static inner class

[advantages:]
    Lazy loading, which instantiates operations only when objects need to be used to improve memory utilization.

[disadvantages:]
    In a multithreaded environment, multiple threads may use objects at the same time. Thread safety needs to be considered to prevent concurrent access from generating multiple instances.

 

(2) Code implementation (static method)
As shown in the following code, objects can only be obtained by “class name. Method name”.

class FullSingleton {
    //Privatized variables cannot be accessed in the form of "class name. Variable name"
    private static FullSingleton fullSingleton;

    //Constructor Privatization (prevent instance objects from being created through new)
    private FullSingleton () {
    }

    //Provide a global access point, which can be accessed through "class name. Variable name"
    public static FullSingleton getInstance() {
        if (fullSingleton == null) {
            fullSingleton = new FullSingleton();
        }
        return fullSingleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        FullSingleton fullSingleton = FullSingleton.getInstance();
        FullSingleton fullSingleton2 = FullSingleton.getInstance();
        System. out. println(fullSingleton == fullSingleton2); //  True is the same object
    }
}

 

(3) Is that it?
Of course not. This is just normal execution in a single threaded environment. Under multi-threaded operation, multiple instances will appear.
For example:
Thread a and thread B execute concurrently to if (fullsingleton = = null). At this time, if the fullsingleton of both threads is null, they will enter the method to execute the new instantiation operation. At this time, multiple instance objects will be generated.
As shown in the following code, it can be found that the objects output by the two threads are inconsistent after multiple code executions.
At this time, the singleton mode is broken and the thread is unsafe.

class FullSingleton {
    //Privatized variables cannot be accessed in the form of "class name. Variable name"
    private static FullSingleton fullSingleton;

    //Constructor Privatization (prevent instance objects from being created through new)
    private FullSingleton () {
    }

    //Provide a global access point. When the method is called, it checks and creates an instance object. Access through "class name. Method name"
    public static FullSingleton getInstance() {
        if (fullSingleton == null) {
            fullSingleton = new FullSingleton();
        }
        return fullSingleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance()); // [email protected]
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance()); // [email protected]
            }
        });

        thread.start();
        thread2.start();
    }
}

 

(4) Code implementation (synchronized synchronization method)
To ensure thread safety, you can use the synchronized keyword to synchronize.
Note:
Synchronized ensures that only one thread can execute a method or a code block at the same time.
As shown in the following code, add a synchronized on the method. It can be found that the objects output by the two threads are always the same after multiple code executions.

class FullSingleton {
    //Privatized variables cannot be accessed in the form of "class name. Variable name"
    private static FullSingleton fullSingleton;

    //Constructor Privatization (prevent instance objects from being created through new)
    private FullSingleton () {
    }

    //Provide a global access point. When the method is called, it checks and creates an instance object. Access through "class name. Method name"
    public static synchronized FullSingleton getInstance() {
        if (fullSingleton == null) {
            fullSingleton = new FullSingleton();
        }
        return fullSingleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance()); // [email protected]
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance()); // [email protected]
            }
        });

        thread.start();
        thread2.start();
    }
}

 

(5) Is that it?
Of course not. Although synchronized is used to ensure thread safety, the lock granularity is too large, which may lead to low execution efficiency.

(6) Code implementation (synchronized code block)
As shown in the following code, in order to narrow the scope of synchronized influence, it can be implemented by using synchronized code blocks within the method.

class FullSingleton {
    //Privatized variables cannot be accessed in the form of "class name. Variable name"
    private static FullSingleton fullSingleton;

    //Constructor Privatization (prevent instance objects from being created through new)
    private FullSingleton () {
    }

    //Provide a global access point. When the method is called, it checks and creates an instance object. Access through "class name. Method name"
    public static FullSingleton getInstance() {
        if (fullSingleton == null) {
            synchronized(FullSingleton.class) {
                fullSingleton = new FullSingleton();
            }
        }
        return fullSingleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance()); // [email protected]
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance()); // [email protected]
            }
        });

        thread.start();
        thread2.start();
    }
}

 

(7) Is that it?
Of course not. This goes back to the problem of thread insecurity mentioned in static methods.
For example:
Thread a and thread B execute concurrently to if (fullsingleton = = null). At this time, if the fullsingleton of both threads is null, they will enter the method. If synchronized is encountered, the new operation will still be executed after synchronous execution to generate multiple instance objects.
At this time, the singleton mode is broken and the thread is unsafe. Double checking can solve this problem.

 

2. Double check

(1) Code implementation
As shown in the following code, double check and add a judgment based on the synchronized synchronization code block.

class FullSingleton {
    //Privatized variables cannot be accessed in the form of "class name. Variable name"
    private static FullSingleton fullSingleton;

    //Constructor Privatization (prevent instance objects from being created through new)
    private FullSingleton () {
    }

    //Provide a global access point. When the method is called, it checks and creates an instance object. Access through "class name. Method name"
    public static FullSingleton getInstance() {
        if (fullSingleton == null) {
            synchronized(FullSingleton.class) {
                if (fullSingleton == null) {
                    fullSingleton = new FullSingleton();
                }
            }
        }
        return fullSingleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance());
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance());
            }
        });

        thread.start();
        thread2.start();
    }
}

 

(2) Is that it?
Of course not. This seems to ensure thread safety, but there is a detail to consider (instruction rearrangement).
As shown below, decompile the code to see the relevant instructions of instantiation operation.

【Test.java】
public class Test {
    public static void main(String[] args) {
        Test test = new Test();
    }
}

【javap -c Test.class】
public class pattern.sington.Test {
  public pattern.sington.Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class pattern/sington/Test
       3: dup
       4: invokespecial #3                  // Method "":()V
       7: astore_1
       8: return
}

[focus on main function:]
    The new instruction allocates memory space for the test object in heap memory.
    Invokespecial instruction to perform instance initialization.
    astore_ 1 instruction to store the stack top reference type value into a variable (even if the object points to heap memory space).
There are three steps:
    1. Allocate memory space.
    2. Instance initialization
    3. Instance points to memory space
Note:
    According to common sense, 1, 2 and 3 are executed in order.
    However, the JVM optimizes instructions (instruction reordering) according to processor characteristics to improve performance.
    Instruction rearrangement means that the instructions may not be executed in the specified order.
    
[return to the code of double check in the above example:]
    In case of instruction rearrangement, the new instantiation operation is executed in the order of 1, 3 and 2. Suppose that thread a has finished executing 1 and 3, but 2 has not finished executing, that is, the object has pointed to the memory space, but has not been initialized. At this time, thread B executes getInstance () code. Since the object points to the memory space, it returns false when judging whether the object is null, and skips the synchronized code block. At this time, the instance object obtained by thread B may have an error (reference escape) because the initialization is not completed.
Note:
    Synchronized is not an atomic operation, and instruction rearrangement may occur.
    Using volatile prevents instruction reordering through a memory barrier.

 

(3) Code implementation (volatile)
Modify variables with volatile to prohibit instruction reordering.

class FullSingleton {
    //Privatized variables cannot be accessed in the form of "class name. Variable name"
    //Volatile prevents instruction rearrangement
    private static volatile FullSingleton fullSingleton;

    //Constructor Privatization (prevent instance objects from being created through new)
    private FullSingleton () {
    }

    //Provide a global access point. When the method is called, it checks and creates an instance object. Access through "class name. Method name"
    public static FullSingleton getInstance() {
        if (fullSingleton == null) {
            synchronized(FullSingleton.class) {
                if (fullSingleton == null) {
                    fullSingleton = new FullSingleton();
                }
            }
        }
        return fullSingleton;
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance()); // [email protected]
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance()); // [email protected]
            }
        });

        thread.start();
        thread2.start();
    }
}

 

3. Static inner class

(1) Basic description
Static inner class is an implementation method that combines the advantages of hungry man mode and lazy man mode.

[core idea:]
    Use private to modify the construction method to ensure the privatization of the construction method.
    Define a static inner class inside the class (it will be loaded only when called), and instantiate the object in the inner class.
    Provide a static public method, and call the static inner class when calling the method. (global access point, get the object through "class name. Method name").    
 
[advantages:]
    Define internal classes and load them only when they are used to realize lazy loading.
    Static is used to define internal classes, and JVM class loading mechanism is used to ensure thread safety.

 

(2) Code implementation
As shown in the following code, define a static inner class.

class FullSingleton {
    //Privatized variables cannot be accessed in the form of "class name. Variable name"
    private static FullSingleton fullSingleton;

    //Constructor Privatization (prevent instance objects from being created through new)
    private FullSingleton () {
    }

    //Provide a global access point. When the method is called, the static internal class is called. Access through "class name. Method name"
    public static FullSingleton getInstance() {
        return InnerInstance.INSTANCE;
    }

    //Define static inner classes
    private static class InnerInstance {
        public static final FullSingleton INSTANCE = new FullSingleton();
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance()); // [email protected]
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(FullSingleton.getInstance()); // [email protected]
            }
        });

        thread.start();
        thread2.start();
    }
}

 

(3) Is that it?
Of course not. The problems of deserialization destruction, reflection destruction and clone destruction also exist.
The solution is similar to that of the hungry man model.

(4) Prevent serialization corruption
Override the readresolve () method to return the instance object.

import java.io.*;

class FullSingleton implements Serializable {
    //Privatized variables cannot be accessed in the form of "class name. Variable name"
    private static FullSingleton fullSingleton;

    //Constructor Privatization (prevent instance objects from being created through new)
    private FullSingleton () {
    }

    //Provide a global access point. When the method is called, the static internal class is called. Access through "class name. Method name"
    public static FullSingleton getInstance() {
        return InnerInstance.INSTANCE;
    }

    //Define static inner classes
    private static class InnerInstance {
        public static final FullSingleton INSTANCE = new FullSingleton();
    }

    private Object readResolve() {
        return getInstance();
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        FullSingleton fullSingleton = FullSingleton.getInstance();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
        oos.writeObject(fullSingleton);

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"));
        FullSingleton fullSingleton2 = (FullSingleton) ois.readObject();
        System.out.println(fullSingleton == fullSingleton2);
    }
}

 

(5) Prevent clone corruption
Override the clone () method to return the instance object.

class FullSingleton implements Cloneable {
    //Privatized variables cannot be accessed in the form of "class name. Variable name"
    private static FullSingleton fullSingleton;

    //Constructor Privatization (prevent instance objects from being created through new)
    private FullSingleton () {
    }

    //Provide a global access point. When the method is called, the static internal class is called. Access through "class name. Method name"
    public static FullSingleton getInstance() {
        return InnerInstance.INSTANCE;
    }

    //Define static inner classes
    private static class InnerInstance {
        public static final FullSingleton INSTANCE = new FullSingleton();
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        return getInstance();
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        FullSingleton fullSingleton = FullSingleton.getInstance();
        FullSingleton fullSingleton2 = (FullSingleton) fullSingleton.clone();
        System.out.println(fullSingleton == fullSingleton2);
    }
}

 

(6) Prevent reflection damage
In the construction method, add a judgment.

import java.lang.reflect.Constructor;

class FullSingleton {
    //Privatized variables cannot be accessed in the form of "class name. Variable name"
    private static FullSingleton fullSingleton;

    //Constructor Privatization (prevent instance objects from being created through new)
    private FullSingleton () {
        if (getInstance() != null) {
            Throw new runtimeException ("instance already exists, creation failed");
        }
    }

    //Provide a global access point. When the method is called, the static internal class is called. Access through "class name. Method name"
    public static FullSingleton getInstance() {
        return InnerInstance.INSTANCE;
    }

    //Define static inner classes
    private static class InnerInstance {
        public static final FullSingleton INSTANCE = new FullSingleton();
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        Class fullSingletonClass = FullSingleton.class;
        Constructor fullSingletonConstructor = fullSingletonClass.getDeclaredConstructor();
        fullSingletonConstructor.setAccessible(true);
        FullSingleton fullSingleton = fullSingletonConstructor.newInstance();

        FullSingleton fullSingleton2 = FullSingleton.getInstance();
        System.out.println(fullSingleton == fullSingleton2);
    }
}

 

4、 Examples

1. Example of singleton mode in JDK (runtime)

(1) Partial source code
The following code shows the implementation of hungry man mode.

public class Runtime {
    private static Runtime currentRuntime = new Runtime();
    public static Runtime getRuntime() {
        return currentRuntime;
    }
    private Runtime() {} 
}