Design mode - singleton mode

Posted by mickro on Wed, 09 Feb 2022 17:57:53 +0100

Singleton mode

1. Overview of singleton mode

For some classes of a software system, only one instance is very important. For example, a system can only have one window manager or file system, only one timing tool, etc.

How to ensure that a class has only one instance and that the instance is easy to access? Defining a unified global variable can ensure that objects can be accessed at any time, but it can not prevent the creation of multiple objects. A better solution is to let the class itself be responsible for creating and saving a unique instance, ensure that no other instances can be created, and provide a method to access the instance. This is the motivation of singleton mode.

The singleton mode is defined as follows:

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

Three key points of singleton mode:

  1. A class can only have one instance
  2. It must create this instance itself
  3. It must provide this instance to the whole system by itself

2. Single instance mode structure

The Singleton pattern contains only one Singleton role, that is, Singleton.

Create a unique instance inside the singleton class and let the client use this instance through the getInstance method. To prevent external instantiation, set the visibility of the constructor to private.

3. Cases

A company has undertaken the development of a server load balancing software, which runs on a load balancing server. Because the servers in the cluster need to be dynamically deleted and the client requests need to be uniformly distributed, it is necessary to ensure the uniqueness of the load balancing server. Design a load balancing server using singleton mode.

3.1 structure diagram

3.2 code implementation

Singleton class

public class LoadBalancer {
    //Store unique instances
    private static LoadBalancer instance = null;
    //Server collection
    private List<String> serverList = null;

    //private constructors 
    private LoadBalancer() {
        serverList = new ArrayList<>();
    }

    //Public static member method, return unique instance
    public static LoadBalancer getLoadBalancer() {
        if (instance == null) {
            instance = new LoadBalancer();
        }
        return instance;
    }

    //Add server
    public void addServer(String server) {
        serverList.add(server);
    }

    //Remove server
    public void removeServer(String server) {
        serverList.remove(server);
    }

    //Get the server randomly using the Random class
    public String getServer() {
        Random random = new Random();
        int i = random.nextInt(serverList.size());
        return serverList.get(i);
    }

}

client

public class Demo {
    public static void main(String[] args) {
        LoadBalancer balancer1,balancer2;
        balancer1=LoadBalancer.getLoadBalancer();
        balancer2=LoadBalancer.getLoadBalancer();

        if (balancer1==balancer2){
            System.out.println("Server load balancing is unique");
        }

        //Add server
        balancer1.addServer("Server 1");
        balancer1.addServer("Server 2");
        balancer1.addServer("Server 3");
        balancer1.addServer("Server 4");

        //Simulate send request
        for (int i = 0; i < 10; i++) {
            String server = balancer1.getServer();
            System.out.println("Send request to server : "+server);
        }
    }
}

3.3 effect display

Server load balancing is unique
Request sent to server: Server 2
Request sent to server: Server 1
Request sent to server: Server 4
Request sent to server: Server 2
Request sent to server: Server 3
Request sent to server: Server 4
Request sent to server: Server 1
Request sent to server: Server 3
Request sent to server: Server 2
Request sent to server: Server 2

4. Hungry man type single case and lazy man type single case

4.1 single case of hungry Han style

Hungry Han single example is the simplest to implement. The structure diagram is as follows:

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

    private EagerSingleton() {}

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

When the class is loaded, the static variable instance will be initialized, which is equivalent to that the singleton object has been created when the class is loaded.

4.2 lazy single case and double check locking

The constructor is also private, which is the same as the hungry man, but the difference is that the lazy singleton instantiates itself when it is first referenced.

The structure is as follows:

public class LazySingleton {
    private static LazySingleton instance = null;

    private LazySingleton() {
    }

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

Lazy singletons are instantiated when the getInstance() method is called for the first time. They are not instantiated when the class is loaded. This technology is also called delayed loading technology, that is, load the instance when necessary. To avoid multiple threads calling at the same time, you can use the synchronized keyword.

In the above mode, although the keyword is added for thread locking to solve the thread safety problem, each call needs to lock the thread, which will greatly reduce the system performance in the multi-threaded and high concurrency environment. Therefore, it can be modified to lock only the key code.

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

The problem seems to be solved, but this is not the case. If the above code is used, there will still be a case where the singleton object is not unique. The reasons are as follows:

If threads A and B are calling getInstance() method at A certain moment, the instance object is null, Can pass "instance==null" "Judgment. Due to the implementation of the locking mechanism, thread A enters the synchronized locked code to execute the instance creation code, and thread B is in the party state and must wait for thread A to finish executing. However, when A finishes executing, B does not know that the instance has been created and will continue to create new instances, resulting in multiple singleton objects, which is contrary to the design idea of singleton mode. Therefore, it is necessary to proceed This step is improved. In synchronized, judge whether the instance is empty once. This method is double check locking.

The improved implementation is as follows:

public class LazySingleton {
    private volatile static LazySingleton instance = null;

    private LazySingleton() {
    }

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

It should be noted that if you use double check locking to implement lazy singleton classes, you need to add the modifier volatile before the static member variable instance. The member variable modified by volatile can ensure that multiple threads can handle it normally. The implementation of volatile is not a perfect way to check the running efficiency of the virtual machine. Therefore, the implementation of volatile may not be a perfect way to check the running efficiency of the virtual machine.

4.3 comparison of the two methods

Hungry Chinese singleton class instantiates itself when it is loaded. Its advantage is that it does not need to consider the problem of multiple threads accessing at the same time, and can ensure the uniqueness of the instance; In terms of call speed and reaction time, since the singleton object can be created from the beginning, it is better than the lazy singleton. However, no matter whether the singleton object needs to be used when the system is running, the object needs to be created when the class is loaded. Therefore, from the perspective of resource utilization efficiency, the hungry man singleton is less than the lazy man singleton, and the loading time may be longer.

The lazy singleton class is created when it is used for the first time. It does not need to occupy system resources all the time and realizes delayed loading. However, it must deal with the problem of simultaneous access of multiple threads. In particular, when the singleton class is used as a resource controller, it must involve resource initialization, which is likely to take a lot of time, which means that there are multiple threads, It needs to be controlled through mechanisms such as double check locking mechanism, which will affect the system performance.

4.4 using static inner classes to implement singleton mode

Hungry Chinese singleton class can not realize delayed loading, and will always occupy memory whether it is used or not in the future; Lazy singleton thread safety control is cumbersome, and the performance is affected. It can be seen that there are some problems in both hungry and lazy types. In order to solve these problems, the singleton mode can be implemented through Initialization on Demand Holder (IoDH) technology in Java language.

In IoDH, you need to add a static internal class to the singleton class, create a singleton object in this class, and then return the object to external use through getInstance() method. The implementation code is as follows:

public class Singleton {
    private Singleton() {
    }

    private static class HolderClass {
        private final static Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return HolderClass.instance;
    }
}

In the above code, there is no static singleton object as a member variable, so the class will not be instantiated during loading. When calling getInstance() for the first time, the internal static class HolderClass will be loaded, and the instance will be defined in the internal class. At this time, the member variable will be initialized first. Java virtual machine will be used to ensure its thread safety and ensure that the variable can only be initialized once.

By using IoDH, you can realize delayed loading, ensure thread safety and do not affect performance. It is the best implementation of Java language singleton mode; Its disadvantage is related to the characteristics of the programming language itself. It is very difficult to support IoDH for object-oriented language.

5. Advantages, disadvantages and applicable environment of single case mode

As a design pattern with clear objectives, simple structure and easy understanding, singleton pattern is used quite frequently in software development and is widely used in many application software and frameworks

5.1 advantages of singleton mode

The advantages of singleton mode are as follows:

  1. Singleton mode provides controlled access to unique instances. Because the singleton pattern encapsulates its unique instance, it can strictly control how and when customers access it.
  2. Because there is only one object in the system memory, it can save system resources. For some objects that need to be created and destroyed frequently, the singleton mode can undoubtedly improve the performance of the system.
  3. Variable instances are allowed. Based on the singleton mode, it can be extended to obtain a specified number of instance objects by using the method similar to the control singleton, which not only saves system resources, but also solves the problem of damaging performance due to too many singleton objects.

5.2 disadvantages of singleton mode

The disadvantages of singleton mode are as follows:

  1. Since there is no abstraction layer in the singleton pattern, it is very difficult to extend the singleton class‘
  2. The responsibility of singleton class is too heavy, which violates the principle of single responsibility to a certain extent. Because the singleton class not only provides business methods, but also provides methods to create objects, coupling the creation of objects with the functions of the objects themselves.
  3. At present, many object-oriented language running environments provide automatic garbage collection technology. Therefore, if the instantiated shared object is not used for a long time, the system will think it is garbage, and will automatically destroy and recycle resources. It will be re instantiated when it is used next time, which will lead to the loss of the state of the shared singleton object.

5.3 applicable environment of singleton mode

Consider using singleton mode in the following cases:

  1. The system only needs one instance object
    There is no abstraction layer in the singleton pattern, so it is very difficult to extend the singleton class‘
  2. The responsibility of singleton class is too heavy, which violates the principle of single responsibility to a certain extent. Because the singleton class not only provides business methods, but also provides methods to create objects, coupling the creation of objects with the functions of the objects themselves.
  3. At present, many object-oriented language running environments provide automatic garbage collection technology. Therefore, if the instantiated shared object is not used for a long time, the system will think it is garbage, and will automatically destroy and recycle resources. It will be re instantiated when it is used next time, which will lead to the loss of the state of the shared singleton object.

5.3 applicable environment of singleton mode

For example, the following modes can be considered:

  1. The system only needs one instance object
  2. A single instance of a client calling class is allowed to use only one public access point. In addition to the public access point, the instance cannot be accessed through other ways.

Topics: Java Design Pattern Interview Multithreading