Singleton

Knowledge:

  • Pattern definition, application scenario, class diagram analysis
  • Bytecode knowledge, bytecode instruction reordering
  • Class loading mechanism
  • JVM serialization mechanism
  • Application of singleton pattern in Spring framework & JDK source code

1. Schema definition

Ensure that there is only one instance of a class and provide a global access point

2. The scene

Heavyweight objects that do not require multiple instances (e.g., thread pools, database connection pools)

3. The class diagram

4. Implementation method

(1) Slacker mode

(1.1) Basic concepts

  • Lazy loading is instantiated only when it is actually used
  • Thread safety
  • Double check, lock optimizationsynchronized
  • The compiler (JIT), the CPU has the possibility to reorder instructions, resulting in use to instances that have not yet been initialized, which can be addedvolatileThe keyword is modified forvolatileDecorates fields to prevent instruction reordering.

(1.2) Code description

  1. Create a singleton pattern
class LazySingleton {
    private static LazySingleton instance;

    /** * private constructors * avoid creating the corresponding instance directly from outside */
    private LazySingleton(a) {}/** * Public global access point *@return* /
    public static LazySingleton getInstance(a) {
        if (instance == null) {
            instance = new LazySingleton();
        }
        returninstance; }}Copy the code

Testing:

LazySingleton instance1 = LazySingleton.getInstance();
LazySingleton instance2 = LazySingleton.getInstance();
System.out.println(instance1 == instance2);
Copy the code

The result is true, indicating that both objects use the same instance

  1. Multithreading causes problems

The above implementation is fine on a single thread, but can cause problems when the situation is multithreaded.

Testing:

new Thread(()-> {
    LazySingleton instance = LazySingleton.getInstance();
    System.out.println(instance);
}).start();

new Thread(()-> {
    LazySingleton instance = LazySingleton.getInstance();
    System.out.println(instance);
}).start();

// Modify the global access point for effect, but it is delayed for a while
public static LazySingleton getInstance(a) {
    if (instance == null) {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        instance = new LazySingleton();
    }
    return instance;
}
Copy the code

Return results: com. Nick. Lazysingleton. Lazysingleton @ 90472 a2 com. Nick. Lazysingleton. Lazysingleton @ 1 e057600 can be seen in a multithreaded order meeting failure condition

  1. Multithreading singleton problem solution

Using synchronized modifiers to introduce locks can avoid the above problems.

public synchronized static LazySingleton getInstance(a) {
    if (instance == null) {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        instance = new LazySingleton();
    }
    return instance;
}
Copy the code

Test results: com. Nick. Lazysingleton. Lazysingleton @ 4388 eabf com. Nick. Lazysingleton. Eabf lazysingleton @ 4388

  1. Further scheme optimization

However, the above solution is not the most perfect, because the global access points are locked and the granularity is a bit large, which may cause problems in high concurrency. Therefore, a further optimization solution is to reduce the lock granularity.

public static LazySingleton getInstance(a) {
    if (instance == null) {
        synchronized (LazySingleton.class) {
            instance = newLazySingleton(); }}return instance;
}
Copy the code

willsynchronizedIn thenewObject, but there are also problems with high concurrency:

To solve this problem, double check is required:

public static LazySingleton getInstance(a) {
    if (instance == null) {
        synchronized (LazySingleton.class) {
            // double check
            if (instance == null) {
                instance = newLazySingleton(); }}}return instance;
}
Copy the code
  1. Bytecode layer optimization

newAn object has a few steps of instructions at the bottom, such as oneDemoClass, when classnewObject, view the instructions through disassembly as follows:The steps can be summarized as follows:

    1. Allocate space
    1. Initialize the
    1. Refer to the assignment

Step 1 must be executed first, while steps 2 and 3 are not in order, so the JIT (just-in-time compilation) or CPU may optimize the instructions and reorder them.

To solve this problem, Java has a volatile modifier that does not order instructions on the reference space. So you can just modify instance with that modifier.

The completion code is as follows:

class LazySingleton {
    /** * Volatile modifier does not reorder the reference space */
    private volatile static LazySingleton instance;

    /** * private constructors * avoid creating the corresponding instance directly from outside */
    private LazySingleton(a) {}/** * Public global access points * private variables are instantiated only when the method is called * multithreading problems without adding synchronized *@return* /
    public static LazySingleton getInstance(a) {
        if (instance == null) {
            synchronized (LazySingleton.class) {
                // double check
                if (instance == null) {
                    instance = newLazySingleton(); }}}returninstance; }}Copy the code

(2) Hungry and Hungry

  • The initialization phase of class loading completes the instance, essentially ensuring that the instance is unique through the JVM class loading mechanism.
  • Class loading process:
  1. Load binary data into memory to generate the corresponding Class data structure
  2. Connect :(1) validate, (2) prepare, assign default values to static member variables of the class, and (3) parse
  3. Initialization: Assigns initial values to static variables of a class

Initializations are triggered only when the corresponding class is actually used (e.g., starting the main class of the current class, direct new operations, accessing static properties, accessing static methods, accessing classes with reflection, initializing a subclass of a class, etc.)

/** * The JVM class loading mechanism to ensure thread safety */
class HungrySingleton {
    private static HungrySingleton instance = new HungrySingleton();

    private HungrySingleton(a) {}public static HungrySingleton getInstance(a) {
        returninstance; }}Copy the code

The static variable instance is initialized (the new object) at the last step of class loading, when the corresponding class is actually used.

(3) Static inner class

  • Essentially, the class loading mechanism is used to ensure thread-safety
  • Initialization is triggered only when it is actually used, so it is also a form of lazy loading
/** * Static inner classes are loaded when the getInstance method is called and returned, and are initialized when static inner classes are loaded, resulting in instance initialization dependent on the JVM class loading mechanism for thread safety */
class InnerClassSingleton {
    private static class InnerClassHolder {
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }

    private InnerClassSingleton(a) {}public static InnerClassSingleton getInstance(a) {
        returnInnerClassHolder.instance; }}Copy the code

(4) Destroy singletons by reflection

When using lazy or inner classes, class loading and initialization is done when getInstance is called. Singletons can be broken by reflection:

// Get the constructor
Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor();
// Obtain permission
declaredConstructor.setAccessible(true);
// Create an instance
InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance();

// Create the instance again with getInstance
InnerClassSingleton instance = InnerClassSingleton.getInstance();
System.out.println(innerClassSingleton == instance);
Copy the code

To avoid this, check in the implementation’s private constructor:

private InnerClassSingleton(a) {
    if(InnerClassHolder.instance ! =null) {
        throw new RuntimeException("Singleton does not allow multiple instances"); }}Copy the code

(5) Break singletons through deserialization

Using an internal static class as an example, let the class inherit from Serializable, which can then be serialized.

class InnerClassSingleton implements Serializable {
    // ...
}
Copy the code

Get an instance using the getInstance method and serialize it to disk:

ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("testSerializable"));
oos.writeObject(instance);
oos.close();
Copy the code

The serialized instance is saved to disk:

It is then loaded back into memory by deserialization and forcibly converted to InnerClassSingleton:

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testSerializable"));
InnerClassSingleton object = ((InnerClassSingleton) ois.readObject());
System.out.println(object == instance);
Copy the code

The forcibly returned instance Object is not the same as the instance created earlier.

This is because when you create an object by deserialization, you don’t call your own constructor. Instead, you pull the data directly from the byte stream.

Workaround: The comment in the Serializable class makes it clear that to avoid this, you can override the method readResolve and return an instance.

Object readResolve(a) throws ObjectStreamException {
    return InnerClassHolder.instance;
}
Copy the code

At this point, deserialize again and judge, and the result is saved:

Exception in thread "main" java.io.InvalidClassException: com.nick.innerclasssingleton.InnerClassSingleton; local class incompatible: stream classdesc serialVersionUID = 2562363086033517702, local class serialVersionUID = 5509890476487421386
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
	at com.nick.innerclasssingleton.InnerClassSingletonTest.main(InnerClassSingletonTest.java:39)
Copy the code

A different version number is displayed because a version number is created based on the data during serialization and stored in the serialized file. When deserializing, the serial number is first generated in the JVM based on the class, and then compared with the sequence number in the file. If the sequence number is consistent, deserialization can be performed. If not, class has changed. So you need to add the version number to the internal static class (see Serializable interface comments).

static final long serialVersionUID = 42L;
Copy the code

Serialize and deserialize again, and the output is true.

(6) Enumerate singletons

public enum  EmunSingleton {
    INSTANCE;
    public void print(a) {
        System.out.println(this.hashCode()); }}Copy the code

Create an enumeration, and can be seen through the disassembly, the compiler will create a class for the enumeration com. Nick. Emunsingleton. Emunsingleton, this class inherits from the abstract class. Java lang. Enum

Testing:

EmunSingleton instance1 = EmunSingleton.INSTANCE;
EmunSingleton instance2 = EmunSingleton.INSTANCE;
System.out.println(instance1 == instance2);
Copy the code

The result is true. If you look at the bytecode of this code, you can see: When the first call EmunSingleton. The INSTANCE to new a com/Nick/EmunSingleton/EmunSingleton object, then saved to the stack, access to the constructor parameters (a String, an int), then calls the constructor. When called the second time, the object is fetched from the static section rather than being new.