Design mode - singleton mode

Design mode – singleton mode

preface:

How will the interviewer ask questions in the singleton mode during the interview? How should we answer? You may encounter these problems during the interview:

  • Why is the starving singleton inherently thread safe?
  • Why is the traditional lazy singleton non thread safe?
  • How to modify the traditional lazy singleton to make its thread safe?
  • Application of double check mode and volatile keyword in singleton mode
  • Application of ThreadLocal in singleton mode
  • Enumerative singleton

How should we answer? Then the answer comes. After reading the next content, you can chat with the interviewer about the single case mode

Introduction to singleton mode

Singleton pattern is a common software design pattern, which belongs to creation pattern. Its meaning is that a class has only one instance and provides an instance for the whole system (providing this instance to the whole system).

Structure:
Three elements of singleton mode:

  • Private construction method
  • Private static instance reference
  • Returns a static public method of a static instance

Advantages of singleton mode:

  • There is only one object in memory to save memory space
  • Avoiding frequent creation and destruction of objects can improve performance
  • Avoid multiple occupation of shared resources and simplify access
  • Provide a global access point for the entire system

Precautions for singleton mode

When using the singleton pattern, we must use the public factory method provided by the singleton class to get the singleton object, instead of using reflection to create it. Using reflection will destroy the singleton pattern and instantiate a new object.

Implementation of single thread

  • Hungry Chinese singleton (load immediately): when the singleton class is loaded, the hungry Chinese singleton instantiates an object and points the reference to the instance.
  • Lazy singleton (delayed loading): an object will be instantiated only when it needs to be used, and the instance to which it refers will be referenced.

From the perspective of speed and response events, hungry Han style (also known as immediate loading) is better; In terms of resource utilization efficiency, lazy (also known as delayed loading) is better.

Hungry Han style single case

public class HungryMan {

    //The class will be created as soon as it is loaded, which may waste space
    private byte[] data1=new byte[1024*1024];
    private byte[] data2=new byte[1024*1024];
    private byte[] data3=new byte[1024*1024];
    private byte[] data4=new byte[1024*1024];

    //Singleton mode is the most important constructor
    private HungryMan(){

    }
	//Private static instance reference creates a private static instance and references the instance to which it refers
    private final static HungryMan HUNGRY_MAN =new HungryMan();

    //Return static public method and static factory method of static instance
    public static HungryMan getInstance(){
        return HUNGRY_MAN;
    }
}

Hungry singleton instantiates an object and refers to the instance when the class is loaded. More importantly, because the class will only be loaded once and created once in the whole life cycle, hungry singleton is thread safe.

Why is the hungry man type singleton inherently thread safe?

Because the class is loaded on demand and only once. Since a class will only be loaded once in the whole life cycle, it has been created before the thread accesses the singleton object, and there is only one instance. That is, the thread can only and must only get this unique object every time.

Traditional lazy singleton implementation

public class LazyMan {

    //Private construction method
    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+"ok");
    }

    //Private static instance reference
    private static LazyMan lazyMan;
	
    //Return static public method and static factory method of static instance
    public static LazyMan getInstance(){
        //When a class object needs to be created, a singleton class object is created and the instance pointed to is referenced 
        //Why judge here? Because lazy loading is delayed loading, unlike hungry loading, it is immediate loading before the thread accesses the object
        //It has already been created. The access object may not have been created yet, so it should be judged that it is not empty
        if(lazyMan==null){
            lazyMan=new LazyMan();
        }
        return lazyMan;
    }
}

Lazy singleton is delayed loading. An object will be instantiated only when necessary, and the object pointed to will be referenced.

Because it is created when needed, it is not safe in a multithreaded environment. Instances may be created concurrently and multiple instances may occur. The original intention of the singleton mode is contrary to the original intention. So how do we avoid it? You can see the implementation form of singleton mode in the multithreaded environment.

So why is the traditional lazy single thread safe?

The main reason for non thread safety is that multiple threads will enter the creation instance if(lazyMan==null) {code block} at the same time. When this happens, the singleton class will create multiple instances, contrary to the original intention of the singleton pattern. Therefore, the traditional lazy singleton is non thread safe.

Multithreading implementation

In a single threaded environment, both hungry and lazy singletons can work normally. However, variation may occur in a multithreaded environment:

  • Starving singleton is inherently thread safe and can be used directly for multithreading without problems
  • Lazy singleton itself is said to be non thread safe, so there will be multiple instances, which is contrary to the original intention of singleton mode.

Then how should we transform on the basis of lazy people?

  • synchronized method
  • synchronized block
  • Deferred loading using internal classes

synchronized static method

public class LazyMan {

    //Private construction method
    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+"ok");
    }

    private static LazyMan lazyMan;

    //Use the synchronized keyword to modify the synchronous mutually exclusive access of critical resources
    public synchronized static LazyMan getInstance(){
        if(lazyMan==null){
            lazyMan=new LazyMan();
        }
        return lazyMan;
    }
}

Use synchronized to modify getInstance() method and lock getInstance() method to realize synchronous and mutually exclusive access to critical resources, so as to ensure single instance.

Although thread safety can be realized, due to the large synchronization scope and coarse locking granularity, the operation efficiency will be very low.

synchronized code block

public class BlockSingleton {

    private static BlockSingleton singleton;

    private BlockSingleton(){

    }

    public static BlockSingleton getInstance(){
        //Using synchronized synchronized code blocks, the locked object is the BlockSingleton class, also known as class lock
        synchronized (BlockSingleton.class){
            if(singleton==null){
                singleton=new BlockSingleton();
            }
        }
        return singleton;
    }
}

In fact, synchronized synchronized code blocks are similar to synchronized static methods, and their efficiency is low.

Deferred loading using internal classes

public class InsideSingleton {

    //Private inner classes are loaded on demand and time-consuming, that is, delayed loading
    private static class Holder {
        private static InsideSingleton insideSingleton=new InsideSingleton();
    }

    private InsideSingleton(){

    }

    public static InsideSingleton getInstance(){
        return Holder.insideSingleton;
    }
}

As shown in the above code, we can use internal classes to implement thread safe lazy singleton. This method is also an efficient method. Its principle is the same as that of hungry singleton, but there may also be reflection attack or deserialization attack.

Double check implementation

Double check diode - volatile

Using dual detection synchronous delayed loading to create a singleton not only ensures the singleton, but also improves the operation efficiency.

//Thread safe lazy singleton
public class DoubleCheckSingleton {

    private static boolean qinjiang = false;

    private static volatile DoubleCheckSingleton singleton;

    private DoubleCheckSingleton() {
        synchronized (DoubleCheckSingleton.class) {
            if(qinjiang==false){
                qinjiang=true;
            }else{
                throw new RuntimeException("Do not attempt to break singleton mode with reflection");
            }
            /*if (singleton != null) {
                throw new RuntimeException("Do not attempt to use reflection to break the singleton pattern "");
            }*/
        }
    }

    public static DoubleCheckSingleton getInstance() {
        //Double check idiom double check mechanism
        if (singleton == null) {
            synchronized (DoubleCheckSingleton.class) {
                if (singleton == null) {
                    singleton = new DoubleCheckSingleton();
                }
            }
        }
        return singleton;
    }

    //Reflection destroys singleton mode
    public static void main(String[] args) throws Exception {
        //DoubleCheckSingleton singleton = DoubleCheckSingleton.getInstance();

        Field qinjiang = DoubleCheckSingleton.class.getDeclaredField("qinjiang");
        qinjiang.setAccessible(true);
        Constructor<DoubleCheckSingleton> declaredConstructor = DoubleCheckSingleton.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true); //Ignore private constructor
        DoubleCheckSingleton singleton1 = declaredConstructor.newInstance();
        qinjiang.setBoolean(singleton1,false);
        DoubleCheckSingleton singleton2 = declaredConstructor.newInstance();

        System.out.println(singleton2 == singleton1); //false
    }

}

In order to improve the operation efficiency in the case of a single instance, we need to check the singleton instance for the second time in order to avoid excessive synchronization (because synchronization only needs to be synchronized when the instance is created for the first time. Once it is created successfully, it is not necessary to obtain locks synchronously when obtaining instances later).

However, it should be noted that the * * volatile keyword must be used to modify the singleton reference, * * why?

If you do not use the volatile keyword, it may lead to an instruction reordering. Before the singleton constructor is executed, the variable singleton may become non null in advance, that is, the assignment statement is invoked before the object is instantiated. At the same time, the other thread will get an incomplete (uninitialized) object, which will cause the system to crash.

This may be a procedure execution step:

  1. Thread 1 enters the getInstance() method. Since the singleton is null, thread 1 enters the synchronized synchronization code block.
  2. Similarly, since singleton is null, thread 1 directly enters singleton=new DoubleCHeckSingleton(), and reordering occurs when the new object, resulting in the execution of the assignment before the constructor, making the instance non null, and the reason why the instance is not initialized (the reason is in NOTE)
  3. At this time, thread 2 checks whether the instance is null. Since the instance is not null, thread 2 gets an incomplete (uninitialized) singleton object
  4. Thread 1 initializes a singleton object by running its constructor.

This security risk is caused by the problem of instruction reordering, and volatile keyword can perfectly solve this problem. Using volatile keyword to modify single instance reference can avoid the above disaster.

NOTE

**The new operation will take three steps, * * expected execution steps:

memory = allocate();// 1. Allocate the memory space of the object

ctorlnstance(memory); //2. Initialization object

singleton = memeory; //3. Make singleton point to the memory address just allocated

In fact, unordered writes (instruction reordering) may occur, which may lead to the following steps:

memory = allocate(); //1. Allocate the memory space of the object

singleton = memeory; //2. Make singleton point to the memory address just allocated

ctorlnstance(memory); //3. Initialization object

Double check idiom - ThreadLocal

With ThreadLocal, we can implement a variant of the double check mode. We localize the critical resource thread. Specifically, in this example, we convert the first layer detection condition if(singleton==null) of double detection into an operation within the local scope of the thread.

public class ThreadLocalSingleton {

    //ThreadLocal thread local variable
    private static ThreadLocal<ThreadLocalSingleton> threadLocal=new ThreadLocal<>();
    private static ThreadLocalSingleton singleton;

    private ThreadLocalSingleton(){

    }

    public static ThreadLocalSingleton getInstance(){
        if(threadLocal.get()==null){ //For the first check, check whether the singleton object exists in the local variable of the current thread
            createSingleton();
        }
        return singleton;
    }

    public static void createSingleton() {
        synchronized (ThreadLocalSingleton.class) {
            if (singleton == null) { //After the second check, the first check indicates that the current thread does not exist and the singleton object is null
                singleton = new ThreadLocalSingleton();
            }
        }
        threadLocal.set(singleton); //Put the singleton object into the local variable of the current thread
    }

    public static void main(String[] args) {
        ThreadLocalSingleton instance = ThreadLocalSingleton.getInstance();
        ThreadLocalSingleton instance1 = ThreadLocalSingleton.getInstance();

        System.out.println(instance==instance1); //true indicates a singleton
    }
}

With the help of ThreadLocal, we can also implement lazy singleton of thread safety, but compared with the direct double check mode, the implementation using ThreadLocal is not as efficient as double Jess check locking.

Enumeration implementation

It not only avoids multi-threaded synchronization problems, but also prevents deserialization and re creation of new objects.

Call directly through Singleton.INSTANCE.whateverMETHOD(). Convenient, concise and safe.

public enum EnumSingleton {

    //Enumeration itself is a Java class, but it just inherits the java.lang.Enum class
    INSTANCE;
    public EnumSingleton getInstance(){
        return INSTANCE;
    }

    /**
     * Enumeration is a reflection of singleton mode by default. Note that singleton of enumeration cannot be destroyed
     */

    public static void main(String[] args) throws Exception {
        EnumSingleton instance1 = EnumSingleton.INSTANCE;
        EnumSingleton instance2 = EnumSingleton.INSTANCE;

        Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingleton enumSingleton = declaredConstructor.newInstance();

        System.out.println(instance1==instance2);
        System.out.println(instance1==enumSingleton);

    }
}

Summary:

Singleton mode is one of the simplest, most basic and most commonly used design modes in java. During operation, ensure that only one instance of a class is created, ensure that there is only one instance of a class, and provide a global access point to access it. This paper introduces various writing methods of singleton mode:

  • Starving singleton (thread safe)
  • Lazy single case
    • Traditional lazy singleton (thread unsafe)
    • Using synchronized synchronous instance method to realize lazy singleton (thread safety)
    • Implementing lazy singleton (thread safety) using synchronized synchronized code blocks
    • Using static inner classes to implement lazy singletons (thread safety)
  • Use double check lock
    • Use volatile keyword (thread safe)
    • Using ThreadLocal to implement lazy singleton (thread safety)
**Summary:**

Singleton mode is java It is one of the simplest, most basic and most commonly used design patterns. During operation, it is ensured that only one instance of a class is created, that only one instance of a class is created, and that a global access point is provided to access it. This paper introduces various writing methods of singleton pattern:

* Starving singleton (thread safe)
* Lazy single case
  * Traditional lazy singleton (thread unsafe)
  * use synchronized Synchronous instance method to realize lazy singleton (thread safety)
  * use synchronized Implementing lazy singleton with synchronous code block (thread safety)
  * Using static inner classes to implement lazy singletons (thread safety)
* Use double check lock
  * use volatile Keywords (thread safe)
  * use ThreadLocal Implement lazy singleton (thread safety)
* Enumerated singleton (thread safe)

Tags: Java Design Pattern Interview

Posted on Mon, 20 Sep 2021 15:09:16 -0400 by DaveSamuel