This is the 12th day of my participation in the August More Text Challenge. For details, see: August More Text Challenge

preface

Singleton pattern: Ensures that there is only one instance of a class and provides a global point of access to that instance

This paper mainly analyzes several common implementations of singleton pattern


A class diagram

The singleton pattern is implemented using a private constructor, a private static variable, and a public static function.

Private constructors guarantee that no object instances can be created by constructors, and only private static variables can be returned by public static functions.


Ii. Implementation

2.1 the hungry

Han instantiates the class as soon as it is loaded, which has the benefit of being thread-safe. However, there are disadvantages. First of all, instantiate the class at load time. If the class occupies a large amount of resources, it will be a waste of resources, since it may not be used at any time, but memory will be used from the beginning.

public class HungryManSingleton {
    private static HungryManSingleton hungryManSingleton = new HungryManSingleton();

    private HungryManSingleton(a) {}public static HungryManSingleton getInstance(a) {
        returnhungryManSingleton; }}Copy the code

Verify the singleton pattern in the main method:

HungryManSingleton instance1 = HungryManSingleton.getInstance();
HungryManSingleton instance2 = HungryManSingleton.getInstance();
System.out.println("Comparison of two instances obtained from hungry Han singleton:" + instance1.equals(instance2));
Copy the code

Output:


Use reflection to break a Hungrier singleton pattern:

// Get the constructor using reflection, break the constructor's privacy, and create an instance using the constructor
Class<HungryManSingleton> singletonClass = HungryManSingleton.class;
Constructor<HungryManSingleton> declaredConstructor = singletonClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);

HungryManSingleton instance1 = HungryManSingleton.getInstance();
HungryManSingleton instance2 = declaredConstructor.newInstance();
System.out.println("Compare with an instance obtained by reflection:" + instance2.equals(instance1));
Copy the code

Output:

As you can see, they are not the same object, which means the Hungrier singleton pattern is broken

In fact, the use of reflection, whether hunhun-style, lazy, upgraded double check locking mechanism, static inner class mechanism, are unsafe


2.2 LanHanShi

In a lazy implementation, the default is no instantiation, when is it used, when is it New, to save resources

public class LazySingleton {
    private static LazySingleton lazySingleton;
    
    private LazySingleton(a) {
        System.out.println(Thread.currentThread().getName());
    }

    public static LazySingleton getInstance(a) {
        if (lazySingleton == null) lazySingleton = new LazySingleton();
        returnlazySingleton; }}Copy the code

But this implementation is not safe in a multi-threaded environment, so imagine if lazySingleton is empty, if lazySingleton is empty and multiple threads pass if (lazySingleton == null), This will cause new to be executed multiple times, using code to reproduce:

public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
        newThread(() -> LazySingleton.getInstance()).start(); }}Copy the code

Console output:

As you can see, the instantiation code is executed three times. There are two ways to solve the thread-safe problem:

  1. ingetInstance()Method by adding keywords to the hierarchysynchronized
  2. Introduce double detection locks


3.3 Double check lock

In order to solve the problem of lazy thread insecurity, the mechanism of double check lock can be introduced. Double check lock is also a kind of lazy loading, and it better solves the problem of low efficiency when ensuring thread safety

Here is the code implementation:

public class DCLSingleton {
    private volatile static DCLSingleton dclSingleton;

    private DCLSingleton(a) {}public static DCLSingleton getInstance(a) {
        if (dclSingleton == null) {
            synchronized (DCLSingleton.class) {
                if (dclSingleton == null) dclSingleton = newDCLSingleton(); }}returndclSingleton; }}Copy the code

In this implementation, compare the lazy method of putting a lock on a method. Then every time that method is called, the lock is acquired, the lock is released, and wait and wait… The double-check lock locks parts of the code. The entry method only enters the synchronized block if the check is null, which is obviously much more efficient

3.3.1 Why double-check

DclSingleton == null == null == null == null == null

If two threads call getInstance() together and both pass the first dclSingleton == null, then the first thread gets the lock, instantiates it and releases it, and then the second thread starts executing, and then immediately instantiates it, too, which is awkward.

So after the second judgment, the first thread made a judgment, okay? Empty, I create one, and then I create an instance and then I release the lock, and then the second thread comes in, hey? I already have it, so I don’t have to create it, release the lock, and happily finish the singleton pattern.


3.3.2 Why use the keyword volatile

For the new operation, which is not an atomic operation, three things happen at the bottom:

  • Allocate memory space in the heap
  • Executes its constructor, initializing the object
  • Define the reference in the stack, and then point the object to the actual object in the heap

We expect it to happen in order, but because the Java command rearrangement mechanism, when could not initialize the object, the stack is defined in a reference to the show in the heap space, when the second thread came in again, first determine whether is empty, he thinks is not null, then will return to the haven’t initialize object; This is where the keyword volatile comes in.


3.4 Static inner class implementation

The static InnerClass InnerClass is not loaded into memory when the InnerClassSingleton class is loaded. The InnerClass is loaded only when the getInstance() method is called to trigger innerclass. INSTANCE, initializing the INSTANCE.

This approach not only has the benefit of delayed initialization, but also provides thread safety support by the virtual machine.

public class InnerClassSingleton {
    private InnerClassSingleton(a) {}public static InnerClassSingleton getInstance(a) {
        return InnerClass.INSTANCE;
    }

    static class InnerClass {
        private static final InnerClassSingleton 
                INSTANCE = newInnerClassSingleton(); }}Copy the code


3.5 the enumeration

This is a best practice for the singleton pattern, which is simple to implement and prevents multiple instantiations in the face of complex serialization or reflection attacks

The external invocation uses Singleton.INSTANCE directly, which is simple and crude.

Since enUms implement the Serializable interface, serialization is not a concern (serialization and deserialization can also cause singletons to fail, but we won’t go into that here), and the JVM can ensure that only one instance is loaded, so it is thread safe. And reflection cannot break the implementation of this singleton pattern

public enum Singleton {
    INSTANCE;
}
Copy the code


conclusion

This article discusses five common implementations of the singleton pattern. In Effect Java, the author strongly advocates the use of enumerated classes to implement the singleton pattern as a best practice for the singleton pattern

Thanks for reading, and I hope you found this article helpful