Singleton pattern

Posted by ywickham on Fri, 21 Jan 2022 18:34:33 +0100

1, Brief description

The singleton pattern is the simplest in Java Design pattern one of. This type of design pattern is a creation pattern, which provides the best way to create objects. This pattern involves a single class that is responsible for creating its own objects while ensuring that only a single object is created. This class provides a way to access its unique object, which can be accessed directly without instantiating the object of this class. That is, two private and one public: ① private constructor ② private static instance attribute ③ public static method to obtain instance.

one ️⃣ be careful

  1. A singleton class can only have one instance.
  2. A singleton class must create its own unique instance.
  3. A singleton class must provide this instance to all other objects.

two ️⃣ Resource loading and performance

Hungry Chinese style instantiates a static object while creating a class. It will occupy a certain memory whether it will be used or not. Accordingly, it is faster on the first call because its resources have been initialized.

Lazy, the loading will be delayed, and the object will be instantiated only when the singleton is used for the first time. Initialization is required during the first call. If more work needs to be done, the performance will be delayed, and then it will be the same as the hungry man style.

Main solution: a globally used class is frequently created and destroyed.
When to use: when you want to control the number of instances and save system resources.
How to solve it: judge whether the system already has this single instance. If yes, it will be returned, and if not, it will be created.
Key code: constructors are private.

three ️⃣ Application examples

  1. A man can only have one wife.
  2. Windows is Multiprocess multithreading It is inevitable that multiple processes or threads operate on a file at the same time, so all files must be processed through a unique instance.
  3. Some device managers are often designed in singleton mode. For example, if a computer has two printers, it must be processed when outputting. Two printers cannot print the same file.

four ️⃣ advantage

  1. There is only one instance in memory, which reduces the memory overhead, especially the frequent creation and destruction of instances (such as the homepage page cache of the school of management).
  2. Avoid multiple occupation of resources (such as writing files).

five ️⃣ shortcoming

There is no interface, no inheritance, and it conflicts with the principle of single responsibility. A class should only care about the internal logic, not how to instantiate it outside.

six ️⃣ Usage scenario

  1. A unique serial number is required for production.
  2. The counters in the WEB do not need to be added to the database every time they are refreshed, but are cached first with a single instance.
  3. Creating an object consumes too many resources, such as I/O Connection with database, etc.

2, Hungry Han style - Thread safety

The hungry man is to initialize the singleton once the class is loaded, so as to ensure that the singleton already exists when getInstance.

  • Description: this method is commonly used, but it is easy to generate garbage objects.
  • Advantages: without locking, the execution efficiency will be improved.
  • Disadvantages: class initialization when loading, wasting memory.
  • It is based on classloder The mechanism avoids the synchronization problem of multithreading. However, instance is instantiated during class loading. Although there are many reasons for class loading. In the singleton mode, most of them call getInstance methods, it is uncertain that there are other ways (or other static methods) to cause class loading. At this time, initializing instance obviously does not achieve the effect of lazy loading.
public class EagerSigleton() {
    //Hold your own reference
    private static final EagerSigleton m_instatnce = new EagerSigleton();
    //Constructor is privatized, and objects cannot be created outside the class at will
    private EagerSigleton() {
    }
    //Provide a global access point to obtain this "unique" object
    public static EagerSigleton getInstance() {
        System.out.println("Load hungry Chinese style....");
        return m_instatnce;
    }
}

Static code block implementation:

public class EagerSigleton{
    private static EagerSigletoninstance;
    static {
        m_instatnce = new EagerSigleton();
    }
    private EagerSigleton() {}
    public static EagerSigletongetInstance() {
        return m_instatnce;
    }
}

This method is actually similar to the above method, except that the process of class instantiation is placed in the Static code block When the class is loaded, the code in the static code block is executed to initialize the class instance. The advantages and disadvantages are the same as above.

3, Lazy - non thread safe

  • Lazy initialization.
  • Description: the most basic implementation method, the biggest problem is that it does not support multithreading. Because I didn't add it synchronized Lock, so strictly speaking, it is not a singleton mode.
  • This method of lazy loading obviously does not require thread safety and cannot work normally in multithreading.
public class LazySigleton(){
  private static LazySigleton instatnce=null;
  // Constructor is privatized, and objects cannot be created outside the class at will
  private LazySigleton(){}
  // Provide a global access point to obtain this "unique" object
  public static LazySigleton getInstance(){      
       if(instatnce == null){               //1: Read the value of instance
          instatnce = new LazySigleton();   //2: Instantiate instance   
        }            
        return instatnce;        
    }
}

Lazy people are lazy. They will initialize this singleton only when getInstance is called.

4, Lazy non thread safety reasons (two points)

For the above code comments, if there are two threads at this time, thread a reads instance null at 1, and then the cpu is robbed by thread B. at this time, thread a has not instantiated instance. Therefore, thread B is still null when reading instance, so it instantiates instance. Then, the cpu time slice turns to thread a. At this point, thread a has read the value of instance and considers it null, instantiating instance again. Therefore, thread a and thread B do not return the same instance.

How to solve it?

  1. Add the synchronized modifier before the method. There will certainly be no thread safety problems.
public class LazySigleton(){
    private static LazySigleton instatnce=null;
    private LazySigleton(){}
    public static synchronized LazySigleton getInstance(){      
      if(instatnce == null){        
         instatnce = new LazySigleton();      
       }            
       return instatnce;        
     }
}

There is a problem with this solution: if 100 threads execute at the same time, each time you execute the getInstance method, you must first obtain the lock and then execute the method body. If there is no lock, you have to wait, which takes a long time, like serial processing.
characteristic:
The performance is not high and the synchronization range is too large. After instantiation, obtaining instances is still synchronous, which is too inefficient. It is necessary to narrow the scope of synchronization.

  1. Add synchronous code blocks to reduce the particle size of locks. Judging whether instance is null is a read operation, and there is no thread safety problem. Therefore, you only need to synchronize the code block of the code that creates the instance, that is, synchronize the code block of the code that may be thread safe.
public class LazySigleton(){
    private static LazySigleton instatnce=null;
    private LazySigleton(){}
    public static LazySigleton getInstance(){      
         if(instatnce == null){ 
            synchronized (LazySigleton.class){       
               instatnce = new LazySigleton();      
            }  
          }          
          return instatnce;        
      }
}

Is there no problem with this? In the same principle, thread a reads the instance value as null. At this time, the cpu is robbed by thread B. thread B judges that the instance value is also null, so it starts to execute the synchronization code block to instantiate the instance. At this time, thread a obtains the cpu. Since thread a has previously determined that the instance value is null, it starts to execute the synchronization code block behind it. It will also instantiate instance. This leads to the creation of two different instances.
characteristic:
Narrow the synchronization range to improve performance, but it is still possible to execute instance=new Singleton() multiple times, which leads to double check.

How to solve the above problems?

Very simply, judge before instance instantiation in the synchronization code block. If instance is null, instantiate it. In this way, you can ensure that instance will be instantiated only once. This is the so-called double check locking mechanism.

Analyze the above scenario again:
Thread a reads that the instance value is null. At this time, the cpu is robbed by thread B, and thread B will judge that the instance value is null. So it starts executing synchronized code blocks and instantiates instance. At this time, thread a obtains the cpu execution right. When thread a executes the synchronous code block, it judges the value of instance. Since thread B has instantiated the shared resource instance after execution, the instance is no longer null. Therefore, thread a will not implement the instantiation code again.

public class LazySigleton() {
    private LazySigleton(){}
    private static LazySigleton instatnce=null;
    public static LazySigleton getInstance(){
        if(instatnce== null) {
            synchronized (LazySigleton.class){
                if (instatnce == null){
                    instatnce = new LazySigleton();
                }
            }
        }
        return instatnce;
    }
}

Double checking and locking does not mean that there must be no thread safety problems. Because, Java Memory Model Processor reordering is not restricted. instatnce = new LazySigleton() is not an atomic statement. In fact, it can be divided into the following steps:

  1. Apply for a piece of memory space;
  2. Instantiate objects in this space;
  3. The reference of instatnce points to the address of this space (after instatnce points to the allocated memory space, it will not be null).
    [it can be understood that the new operation in Java is not atomic]

The problems of instruction reordering are:
For the above steps, it is likely that the instruction reordering is not performed in sequence according to the above steps [1, 2 and 3]. For example, first execute 1 to apply for a piece of memory space, and then execute 3. The reference of instatnce points to the memory space address just applied. Then, when it executes 2 and judges instatnce, because instatnce has pointed to an address, it will no longer be null. Therefore, it will not instantiate the object. This is the so-called instruction reordering security problem. So, how to solve this problem?

add Keyword volatile Because volatile prevents instruction reordering. Volatile can ensure the execution sequence of [1, 2, 3]. If 1 and 2 are not executed, 3 will not be executed, that is, if 1 and 2 are not executed, instance will always be empty. In this way, you can ensure that 3(instance assignment operation) is the last step, so that the instance will not be null when the object is not initialized. In this way, the correct singleton mode is realized. The specific codes are as follows:

public class LazySigleton() {
    private LazySigleton(){}
    private static volatile LazySigleton instatnce=null;
    public static LazySigleton getInstance(){
        if(instatnce== null) {
            synchronized (LazySigleton.class){
                if (instatnce == null){
                    instatnce = new LazySigleton();
                }
            }
        }
        return instatnce;
    }
}

Attached:
one ️⃣ Static internal lazy pattern

public class Singleton{
	private Singleton(){}
	public static Singleton getInstance(){
		return InstanceHolder.instance;
	}
	static class InstanceHolder{
		private static Singleton instance=new Singleton();
	}
}

The static internal class will not be loaded when there is no display call. When it is executed
return InstanceHolder.instance before loading initialization, so as to realize the correct singleton mode.
two ️⃣ utilize enumeration The characteristics of JVM Layer guaranteed absolute singleton

class EnumSingleton {
      //Private constructor to prevent new objects
      private EnumSingleton() {}
      public static EnmuSingleton getInstance() {
          return Singleton.INSTANCE.getSingleton();
      }
      //JVM layer guarantees absolute singleton
      private enum Singleton {
          INSTANCE;
          private EnumSingleton singleton;
          Singleton() {
                singleton = new EnumSingleton();
          }
          public EnumSingleton getSingleton() {
                  return singleton;
          }
      } 
}

Why is a single instance of enumeration perfect? The enumeration itself cannot pass reflex,clone,Deserialization Wait, initialize the object.

Topics: Java Singleton pattern DesignPattern