The singleton pattern of design pattern will help you thoroughly understand the hungry and lazy patterns, and be able to write the singleton pattern by hand

Singleton mode

What is singleton mode

We know that the singleton pattern, as its name implies, is that a class has only one instance object, and the instance object of this class cannot be created through new. When the external wants to get the instance of this class, it cannot be obtained directly. It needs to call the method getInstance of this class to get this unique instance object.

From the above paragraph, we can analyze several characteristics of singleton mode

  • A class has only one instance object and cannot be accessed directly = > this instance object must be static and private
  • Cannot create instance object through new = > the constructor of this class is private

Through the above analysis, we can get a simple singleton pattern

public class Singleton {

    private static Singleton SINGLETON = new Singleton();

    private Singleton(){}

    public static HungrySingleton getInstance(){
        return HUNGRY_SINGLETON;
    }
}

Classification of singleton patterns

Consider a question: if a class uses singleton mode, but the instance of this class is very resource consuming, such as the following

public class Singleton {

    private final byte[] resource = new byte[1024 * 1024 * 1024 * 10 ];
    private static Singleton SINGLETON = new Singleton();

    private Singleton(){}

    public static HungrySingleton getInstance(){
        return HUNGRY_SINGLETON;
    }
}

This class generates the corresponding instance object when it is loaded, but we don't use this singleton object at the beginning, which will cause a waste of memory

Therefore, we only want to generate the instance object when we get the instance

At this time, the singleton mode is divided into two categories:

  • Hungry Chinese style: instantiate the object at the beginning, that is, the implementation of the above singleton pattern
  • Lazy: create an instance object when getInstance

Lazy singleton mode

First implementation

According to the previous idea, we make the following modifications to the code to become lazy singleton mode

public class LazySingleton1 {

    private static LazySingleton1 LAZY_SINGLETON;

    private LazySingleton1(){}

    public LazySingleton1 getInstance(){
        if (LAZY_SINGLETON == null){
            LAZY_SINGLETON = new LazySingleton1();
        }
        return LAZY_SINGLETON;
    }

}

When obtaining an instance, we judge whether the instance object is empty. If it is empty, we create a new instance object

However, this code still has some problems:

In the case of a single thread, there is no problem, but if multiple threads are used to call the getInstance method at the same time, it will appear

# Threads A and B run getistance method at the same time

thread  A judge LAZY_SINGLETON by null
 thread  B judge LAZY_SINGLETON by null

thread  A Create instance object 1
 thread  A Get instance object 1

thread  B Create instance object 2
 thread  B Get instance object 2

At this point, they do not get the same instance object, which violates the concept of singleton mode

The second implementation

In this case, we naturally think of locking to ensure that the object is created only once. The code is as follows

public class LazySingleton2 {

    private static LazySingleton2 LAZY_SINGLETON;

    private LazySingleton2(){}

    //    Double detection lock DCL
    public LazySingleton2 getInstance(){
        if (LAZY_SINGLETON == null){
            synchronized (LazySingleton2.class){
                if(LAZY_SINGLETON == null){
                    LAZY_SINGLETON = new LazySingleton2();
                }
            }
        }
        return LAZY_SINGLETON;
    }

}

Here we explain why judgment should be added inside and outside the synchronization code block. Let's assume that there is no judgment inside the synchronization code block

public LazySingleton2 getInstance(){
    if (LAZY_SINGLETON == null){
        synchronized (LazySingleton2.class){
            LAZY_SINGLETON = new LazySingleton2();
        }
    }
    return LAZY_SINGLETON;
}
# Threads A and B run getistance method at the same time

thread  A judge LAZY_SINGLETON by null
 thread  B judge LAZY_SINGLETON by null

thread  A Get the lock, thread B Blocked
 thread  A Create instance object 1
 thread  A Release lock

thread  B Create instance object 2
 thread  B Get instance object 2

At this time, thread A and thread B do not get the same instance object, so internal judgment is required

As for external judgment, it is to improve efficiency. Without judgment, each thread calling getInstance method enters the synchronization code block and blocks other threads, which will affect efficiency to a certain extent

Although the code at this time is very complete, there are still some problems.

The third implementation

public class LazySingleton3 {

    private static volatile LazySingleton3 LAZY_SINGLETON;

    private LazySingleton3(){}

    //    Double detection lock DCL
    public LazySingleton3 getInstance(){
        if (LAZY_SINGLETON == null){
            synchronized (LazySingleton3.class){
                if(LAZY_SINGLETON == null){
                    LAZY_SINGLETON = new LazySingleton3();
                }
            }
        }
        return LAZY_SINGLETON;
    }

}

We can see that the third implementation knowledge adds a volatile keyword to the variable

volatile features:

  • Ensure visibility
  • Atomicity is not guaranteed
  • Prohibit instruction rearrangement

We use volatile's feature of prohibiting instruction rearrangement

**Instruction rearrangement: * * reorder instructions without affecting execution results

The creation and reference of objects can be roughly understood as the following three stages:

  1. Apply for space for objects
  2. Initialize memory space
  3. Variable points to object

In the case of instruction rearrangement, it is possible to rearrange to 3, 1 and 2

If a thread executes 3 when creating an object, because 1 and 2 have not been executed, another thread determines that the variable point is null and creates the object again, which makes the objects obtained by the two threads different.

Therefore, it is necessary to add volatile keyword

summary

  • The singleton mode is divided into hungry man mode and lazy man mode
  • Double lock detection (DCL) is used when lazy fetching instances
  • Lazy variables need to add volatile keyword

In addition, the singleton mode may be destroyed through reflection. At this time, the solution is to create a getInstance method using Enum enumeration. The underlying implementation of enumeration is still a class. Its constructor prohibits the use of reflection, otherwise it will run out of exceptions, which completely solves the security problem.

Tags: Java Design Pattern Singleton pattern

Posted on Fri, 26 Nov 2021 06:05:02 -0500 by Round