23 design modes - single case mode

Posted by Sonu Kapoor on Mon, 31 Jan 2022 01:44:59 +0100

Singleton mode

Reference documents

Singleton Pattern is one of the simplest design patterns in Java. 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.

be careful:

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

1, Introduction

Intent:

Ensure that a class has only one instance and provide a global access point to access it.

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

Judge whether the system already has this singleton. If so, return. If not, create it.

critical code

Constructors are private.

Application examples

  • There is only one head teacher in a class.
  • Windows is multi process and multi-threaded. When operating a file, it is inevitable that multiple processes or threads operate a file at the same time, so all files must be processed through a unique instance.
  • Some device managers are often designed in singleton mode. For example, a computer has two printers, which must be processed when outputting. Two printers cannot print the same file.

advantage

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

shortcoming

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

Usage scenario

  • A unique serial number is required for production.
  • 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.
  • Creating an object consumes too many resources, such as the connection between I/O and database.

matters needing attention

The getInstance() method needs to use the synchronization lock synchronized (Singleton.class) to prevent multiple threads from entering at the same time, causing the instance to be instantiated multiple times.

2, Realize

We will create a SingleObject class. The SingleObject class has its private constructor and a static instance of itself.

The SingleObject class provides a static method for the outside world to obtain its static instance. The SingletonPatternDemo class uses the SingleObject class to get the SingleObject object object.

Step 1

Create a Singleton class. SingleObject.java

public class SingleObject {
 
   //Create an object of SingleObject
   private static SingleObject instance = new SingleObject();
 
   //Make the constructor private so that the class will not be instantiated
   private SingleObject(){}
 
   //Gets the only available object
   public static SingleObject getInstance(){
      return instance;
   }
 
   public void showMessage(){
      System.out.println("Hello World!");
   }
}

Step 2

Get the unique object from the singleton class. SingletonPatternDemo.java

public class SingletonPatternDemo {
   public static void main(String[] args) {
 
      //Illegal constructor
      //Compile time error: constructor SingleObject() is not visible
      //SingleObject object = new SingleObject();
 
      //Gets the only available object
      SingleObject object = SingleObject.getInstance();
 
      //display messages
      object.showMessage();
   }
}

Step 3

Execute the program and output the results:

Hello World!

3, Several implementation methods of singleton mode

There are many ways to implement the singleton mode, as shown below:

1. Hungry Han style

Lazy initialization: no

Multithread safe: Yes

Implementation difficulty: easy

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 the classloader mechanism to avoid the synchronization problem of multiple threads. However, instance is instantiated during class loading. Although there are many reasons for class loading, most of them call getInstance method in singleton mode, it is not sure 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.

Example 1

public class singleton01 {

    //Define a static instance
    private static final singleton01 INSTANCE = new singleton01();

    //Structural privatization
    private singleton01() {
    }

    //getter method
    public static singleton01 getInstance(){
        return INSTANCE;
    }

    //Common method
    public void m(){
        System.out.println("m");
    }

    //Main function
    public static void main(String[] args) {

        singleton01 s1 = singleton01.getInstance();
        singleton01 s2 = singleton01.getInstance();
        //Two references point to the same object
        System.out.println(s1 == s2);
    }
}

Example 2: static code block

public class singleton02 {
    //Define a static instance
    private static final singleton02 INSTANCE;

    //Complete initialization in static code block
    static{
        INSTANCE = new singleton02();
    }

    //Structural privatization
    private singleton02(){}

    //getter method
    public static singleton02 getInstance(){
        return INSTANCE;
    }

    //Common method
    public void m(){
        System.out.println("m");
    }

    //Main function
    public static void main(String args[]){
        singleton02 s1 = singleton02.getInstance();
        singleton02 s2 = singleton02.getInstance();
        //Two references point to the same object
        System.out.println(s1 == s2);
    }
}

2. Lazy, thread unsafe

Whether to initialize Lazy: Yes

Multithread safe: no

Implementation difficulty: easy

Description: this method is the most basic implementation method. The biggest problem of this implementation is that it does not support multithreading. Because there is no lock synchronized, it is not a singleton mode in a strict sense.
This way of lazy loading is obvious. It does not require thread safety and cannot work normally in multithreading.

public class singleton03 {
    //Define a static instance
    private static singleton03 INSTANCE;

    //Structural privatization
    private singleton03(){}

    public static singleton03 getINSTANCE() {
        //judge
        if(INSTANCE == null){
            try {
                //Multithreading test
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //If it is empty, instantiate
            INSTANCE = new singleton03();
        }
        //If it is not empty, the last INSTANCE will be returned
        return INSTANCE;
    }

    //Common method
    public void m(){
        System.out.println("m");
    }

    //Main function, testing 100 threads
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            //Lambda expression, print out the hashCode of singleton03
            new Thread(()->
                System.out.println(singleton03.getINSTANCE().hashCode())
            ).start();
        }
    }
}

The following implementation methods support multithreading, but there are differences in performance.

3. Lazy, thread safe

Whether to initialize Lazy: Yes

Multithread safe: Yes

Implementation difficulty: easy

Description: this method has good lazy loading and can work well in multithreading. However, the efficiency is very low and synchronization is not required in 99% of cases.
Advantages: initialize only after the first call to avoid memory waste.
Disadvantages: you must lock synchronized to ensure single instance, but locking will affect efficiency.
The getInstance() method is not used too often for application performance.

public class singleton04 {
    //Define a static instance
    private static singleton04 INSTANCE;

    //Structural privatization
    private singleton04(){}

    public static synchronized singleton04 getINSTANCE() {
        //judge
        if(INSTANCE == null){
            try {
                //Multithreading test
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //If it is empty, instantiate
            INSTANCE = new singleton04();
        }
        //If it is not empty, the last INSTANCE will be returned
        return INSTANCE;
    }

    //Common method
    public void m(){
        System.out.println("m");
    }

    //Main function, testing 100 threads
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            //Lambda expression, print out the hashCode of singleton03
            new Thread(()->
                    System.out.println(singleton04.getINSTANCE().hashCode())
            ).start();
        }
    }
}

It is not feasible to reduce synchronous code blocks to improve efficiency

public class singleton05 {
    //Define a static instance
    private static singleton05 INSTANCE;

    //Structural privatization
    public singleton05() {}

    public static singleton05 getINSTANCE() {
        //judge
        if (INSTANCE == null){
            //It is not feasible to reduce synchronous code blocks to improve efficiency
            synchronized (singleton05.class){
                //Multithreading test
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //If it is empty, instantiate
                INSTANCE = new singleton05();
            }
        }
        //If it is not empty, the last INSTANCE will be returned
        return INSTANCE;
    }
    
    //Common method
    public void m() {
        System.out.println("m");
    }
    
    //Main function, testing 100 threads
    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(singleton05.getINSTANCE().hashCode());
            }).start();
        }
    }
}

4. Double checked locking (DCL)

JDK version: jdk1 5 cases

Whether to initialize Lazy: Yes

Multithread safe: Yes

Implementation difficulty: relatively complex

Description: this method adopts double lock mechanism, which is safe and can maintain high performance in the case of multithreading.
The performance of getInstance() is critical to the application.

public class singleton06 {
    //Add volatile keyword to avoid instruction rearrangement
    private static volatile singleton06 INSTANCE;

    //Structural privatization
    public singleton06() {
    }

    public static singleton06 getINSTANCE() {
        //First judgment
        if(INSTANCE == null){
            synchronized (singleton06.class){
                //Double judgment
                if (INSTANCE == null){
                    try {
                        //Multithreading test
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //If it is empty, instantiate
                    INSTANCE = new singleton06();
                }
            }
        }
        //If it is not empty, the last INSTANCE will be returned
        return INSTANCE;
    }
    
    //Common method
    public void m() {
        System.out.println("m");
    }

    //Main function, testing 100 threads
    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(singleton06.getINSTANCE().hashCode());
            }).start();
        }
    }
}

5. Registered / static internal class

Whether to initialize Lazy: Yes

Multithread safe: Yes

Implementation difficulty: General

Description: this method can achieve the same effect as the double check lock method, but the implementation is simpler. When using delayed initialization for static domains, this method should be used instead of double check locking. This method is only applicable to the static domain. The double check lock method can be used when the instance domain needs to delay initialization.
This method also uses the classloader mechanism to ensure that there is only one thread when initializing an instance. It is different from the first method: in the first method, as long as the Singleton class is loaded, the instance will be instantiated (not achieving the lazy loading effect). In this way, the Singleton class is loaded, and the instance does not have to be initialized. Because the singleton07Holder class is not actively used, the singleton07Holder class will be explicitly loaded to instantiate the instance only when the getInstance method is explicitly called. Imagine if instantiating an instance consumes a lot of resources, so you want it to delay loading. On the other hand, you don't want to instantiate it when the Singleton class is loaded, because you can't ensure that the Singleton class may be actively used in other places and loaded, so instantiating an instance at this time is obviously inappropriate. At this time, this method is very reasonable compared with the first method.

public class singleton07 {
    //Structural privatization
    private singleton07(){}

    //Create static inner class
    private static class singleton07Holder{
        private final static singleton07 INSTANCE = new singleton07();
    }

    //Call the INSTANCE of the static inner class to create an INSTANCE
    public static singleton07 getInstance(){
        return singleton07Holder.INSTANCE;
    }
    
    //Common method
    public void m() {
        System.out.println("m");
    }

    //Main function, testing 100 threads
    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(singleton07.getInstance().hashCode());
            }).start();
        }
    }
}

6. Enumeration

JDK version: jdk1 5 cases

Lazy initialization: no

Multithread safe: Yes

Implementation difficulty: easy

Description: this implementation has not been widely used, but it is the best way to implement singleton mode. It is more concise, automatically supports serialization mechanism and absolutely prevents multiple instantiations.
This method is advocated by Josh Bloch, the author of Effective Java. It can not only avoid the problem of multi-threaded synchronization, but also automatically support the serialization mechanism to prevent deserialization, re create new objects, and absolutely prevent multiple instantiations. However, due to jdk1 The enum feature was added after 5. It feels strange to write in this way, and it is rarely used in practical work.
Private constructor cannot be called through reflection attack.

public enum singleton08 {
    //enumeration
    INSTANCE;

    //Common method
    public void m() {}

    //Main function, testing 100 threads
    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(singleton08.INSTANCE.hashCode());
            }).start();
        }
    }
}

Generally, the second and third lazy ways are not recommended, and the first hungry way is recommended. The fifth registration method can only be used when the lazy loading effect is to be clearly realized. If it involves deserialization to create objects, you can try to use the sixth enumeration method. If there are other special needs, the fourth double check lock mode can be considered.

Topics: Java Singleton pattern