Design mode - singleton mode

Posted by sinisake on Wed, 09 Feb 2022 17:20:09 +0100

Concept: ensure that there is only one instance of a class and provide a global access point to access this unique instance

ps: Singleton mode is an object creation mode. Singleton mode has three main points: first, a class can only have one singleton, second, it must create this instance by itself, and third, it must provide this instance to the whole system by itself.

Singleton mode implementation

The purpose of singleton mode is to ensure that a class has only one instance and provide a global access point to access it. Singleton mode contains only one role, that is, singleton. Singleton has a private constructor to ensure that users cannot instantiate it directly through the new key word. In addition, The singleton class also contains a static private member variable and a static public factory method. The factory method is responsible for verifying the existence of the instance and instantiating itself, and then stored in the static member variable to ensure that only one instance is created.

Hungry Han style

When the class is loaded, the static variable instance will be initialized. At this time, the private constructor of the class will be called, and the only instance of the singleton class will be created

package singleton;

public class EagerSingleton {
   private static final EagerSingleton instance=new EagerSingleton();
   private  EagerSingleton(){

   }
   public static EagerSingleton getInstance(){
       return instance;
   }

    public static void main(String[] args) {
        EagerSingleton s1,s2;
        s1=EagerSingleton.getInstance();
        s2=EagerSingleton.getInstance();
        System.out.println(s1==s2);
    }
}

Lazy style

The same as the starving singleton class, the constructor of the lazy singleton class is also private. The difference between the starving singleton class and the lazy singleton class is that the lazy singleton class instantiates itself when it is referenced for the first time and will not instantiate itself when the lazy singleton mode is loaded.

public class LazySingleton {
    private static LazySingleton instance =null;
    private LazySingleton(){
        
    }
    
    //The synchronized keyword locks the method to ensure that only one thread can execute the scheme at any time
    synchronized public static LazySingleton getInstance(){
        if (instance==null){
            instance=new LazySingleton();
        }
        return instance;
    }
}

In the above lazy singleton class, the keyword synchronized is added in front of getInstance () method for thread locking to deal with the problem of simultaneous access of multiple threads. Although the above code solves the problem of thread safety, it is necessary to judge the thread locking every time getInstance () is called, In the environment of multithreading and high concurrent access, the system performance will be greatly reduced, so we can continue to improve the lazy singleton mode

public static LazySingleton getInstance(){
        if (instance==null){
            synchronized (LazySingleton.class){
                instance=new LazySingleton();
            }
            
        }
        return instance;
    }

duplication check

The above problems seem to be solved, but this is not the case. If the above code is used to implement the singleton class, there will still be cases where the singleton object is not unique. The reasons are as follows:

If both thread A and thread B are calling getInstance () method at A certain moment, and the instance object is null, it can be judged by instance==null. Due to the implementation of the synchronized locking mechanism, thread A enters the synchronized locked code to execute the instance creation code, and thread B is in the queue state, You must wait for thread A to execute before entering the synchronized locked code.

However, when A completes execution, thread B does not know that the instance has been created and will continue to create new instances, resulting in multiple singleton objects, which violates the design idea of singleton mode. Therefore, it needs to be further improved. Judge instance ==null again in synchronized. This method is called Double check locking

public class DoubleCheck {
    private volatile static DoubleCheck instance=null;
    private DoubleCheck(){

    }
    public static DoubleCheck getInstance(){
        //First judgment
        if (instance ==null){
            //Lock code block
            synchronized (DoubleCheck.class){
                //Second judgment
                if (instance==null){
                    instance=new DoubleCheck();//Create a singleton instance
                }
            }
        }
        return instance;
    }
}

Why add volatile? First, understand what new Singleton() does.

new an object has several steps.

  • 1. Check whether the class object is loaded. If not, load the class object first,
  • 2. Allocate memory space, initialize instances,
  • 3. Call the constructor,
  • 4. Return the address to the reference.

In order to optimize the program, the cpu may reorder instructions, disrupting these 3 and 4 steps, resulting in the instance memory being used before it is allocated.

Thread A and thread B are used as examples. Thread A executes new Singleton() and starts initializing the instance object. Due to the reordering of instructions, this new operation assigns the reference first and does not execute the constructor. When the time slice is over, switch to thread B for execution. Thread B calls the new Singleton() method. If it finds that the reference is not null, it directly returns the reference address. Then thread B performs some operations, which may cause thread B to use variables that have not been initialized.

Adding volatile ensures that new will not be reordered by the instruction Prevent instruction rearrangement

So far, this is a complete lazy mode - > thread safe - > double check and lock singleton mode.

Static inner class

Starving singleton class can not realize delayed loading. It will always occupy memory whether it is used or not in the future. The thread safety control of starving singleton class is cumbersome, and the performance is affected. It can be seen that there are some problems in both starving singleton and lazy singleton. In order to overcome these problems, the singleton mode can be realized through IoDH technology in Java language

In IoDH, you need to add a static inner class to the singleton class, create a singleton object in the modified inner class, and then return the singleton object to the outside through the getInstance () method

//IoDH
public class Singleton {

    private Singleton(){

    }
    //Static inner class
    private static class HolderClass{
        private final static  Singleton instance =new Singleton();
    }
    public static Singleton getInstance(){
        return HolderClass.instance;
    }

    public static void main(String[] args) {
        Singleton s1,s2;
        s1=Singleton.getInstance();
        s2 =Singleton.getInstance();
        System.out.println(s1 ==s2);
    }
}

Since the static Singleton object is not directly instantiated as a member variable of Singleton, Singleton will not be instantiated during class loading. When calling getInstance for the first time, the internal class HolderClass will be loaded, in which a static variable instance is defined. At this time, the member variable will be initialized first, and the java virtual machine will ensure its thread safety, Ensure that the member variable can only be initialized once. Since the getInstance method does not have any thread locking, its performance will not be affected.

Using IoDH can not only realize delayed loading, but also ensure thread safety and do not affect the system performance. It is the best implementation method of java language singleton mode. Its disadvantage is related to the characteristics of the programming language itself, and many object-oriented do not support IoDH

Topics: Java Design Pattern