Singleton pattern: ensure that a class has only one instance, and instantiate itself and provide this instance to the whole system.
Singleton pattern is a kind of creation pattern, which is the simplest design pattern in creation pattern
Used to create objects that are unique in the software system.
Although the singleton mode is very simple, it is still used frequently.
Learning difficulty: ★☆☆☆☆ | Frequency of use: ★★★★★ |
---|
1, Motivation of singleton mode
I'm sure you are familiar with task manager. You can try it with your own computer. Click "task manager" in the right-click menu of windows taskbar many times to see if you can open multiple task manager windows. Under normal circumstances, no matter how many times the task manager is started, the windows system will always open only one task manager window, that is, in a Windows system, the task manager is unique.
Similar situations are often encountered in actual development. In order to save system resources, it is sometimes necessary to ensure that there is only one instance of a class in the system. When the unique instance is created successfully, it is impossible to create another object of the same type, and all operations can only be based on this unique instance. In order to ensure the uniqueness of the object, it can be realized through the singleton mode, which is the motivation of the singleton mode.
2, Overview of singleton mode
1. Definition of singleton mode
Singleton Pattern: ensure that a class has only one instance, and instantiate it by itself and provide this instance to the whole system. This class is also called singleton class, which provides global access methods. Singleton mode is an object creation mode.
2. Three key points of singleton mode
- A class can only have one instance
- It must create this instance itself
- It must provide the whole system with its own instance
3. Structural drawing
As can be seen from the above figure, the singleton class pattern structure diagram contains only one singleton role.
Singleton:
- In the internal implementation of the singleton class, only one instance is generated. At the same time, it provides a static GetInstance() method so that customers can access its unique instance.
- To prevent singleton instantiation externally, its constructor visibility is private.
- A static object of Singleton type is defined inside the Singleton class as the only instance for external sharing access.
3, Design of load balancer
1. Demand
A technology company has undertaken the development of a server Load Balance software. The software runs on a Load Balance server, which can distribute concurrent access and data traffic to multiple devices in the server cluster for concurrent processing, improve the overall processing capacity of the system and shorten the response time. 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 balancer, that is, there can only be one load balancer to be responsible for the management of the server and the distribution of requests, otherwise it will lead to problems such as inconsistent server States and conflicting request allocation. How to ensure the uniqueness of load balancer is the key to the success of the software.
2. Structural drawing
Through analysis and trade-offs, the developers of the R & D Department of A technology company decided to use the single case mode to design the load balancer. The structure is shown in the figure below:
3. Realization
In the above structure diagram, the load balancer LoadBalancer is designed as a singleton class, which contains a collection serverList that stores server information. Each time, a server is randomly selected in the serverList to respond to the request of the client. The implementation code is as follows:
/// <summary> ///Load balancer: singleton class. The real environment may be very complex. Only some code related to patterns are listed here /// </summary> public class LoadBalancer { //Private static member variables that hold unique instances private static LoadBalancer instance = null; //Server collection private List<string> serverList = null; /// <summary> ///Private constructor /// </summary> private LoadBalancer() { serverList = new List<string>(); } /// <summary> ///Public static member method, return unique instance /// </summary> /// <returns></returns> public static LoadBalancer GetLoadBalancer() { if (instance == null) instance = new LoadBalancer(); return instance; } //Add server public void AddServer(string server) { serverList.Add(server); } //Delete server public void RemoveServer(string server) { serverList.Remove(server); } //Get the server randomly using the Random class public string GetServer() { var random = new Random(); var i = random.Next(serverList.Count); return serverList[i]; } }
Client test code:
class Program { static void Main(string[] args) { //Create 4 LoadBalancer objects LoadBalancer balancer1, balancer2, balancer3, balancer4; balancer1 = LoadBalancer.GetLoadBalancer(); balancer2 = LoadBalancer.GetLoadBalancer(); balancer3 = LoadBalancer.GetLoadBalancer(); balancer4 = LoadBalancer.GetLoadBalancer(); //Determine whether the server load balancer is the same if (balancer1 == balancer2 && balancer2 == balancer3 && balancer3 == balancer4) { Console.WriteLine("Server load balancer is unique!"); } //Add server balancer1.AddServer("server 1"); balancer1.AddServer("server 2"); balancer1.AddServer("server 3"); balancer1.AddServer("server 4"); for (int i = 0; i < 10; i++) { var server = balancer1.GetServer(); Console.WriteLine("Distribute request to server:" + server); } } }
Compile and run the program, and the results are as follows:
From the running results, we can see that although we created four LoadBalancer objects, they are the same object. Therefore, the uniqueness of the LoadBalancer object can be ensured by using the singleton pattern.
4, Hungry man type single case and lazy man type single case
The developers of the R & D department used the singleton mode to realize the design of the load balancer, but there was a very serious problem in practical use. When the user starts the load balancer again during the startup of the load balancer, the system has no exception, but the request distribution fails when the client submits the request. Through careful analysis, it is found that there are still multiple load balancer objects in the original system, resulting in inconsistent target servers during distribution, resulting in conflicts.
Now the implementation code of load balancer is analyzed again. When the GetLoadBalancer() method is called for the first time to create and start the load balancer, the instance object is null, so the system will execute the code instance=new LoadBalancer(). In this process, it takes a period of time to create the LoadBalancer object due to a lot of initialization of the LoadBalancer. At this time, if GetLoadBalancer() method is called again (usually in multi-threaded environment), because instance has not been created successfully, it is still null, the judgment condition "instance==null" is still true, and the code instance=new LoadBalancer() will be executed again, resulting in the creation of multiple instance objects, which is contrary to the original intention of singleton mode and leads to system operation errors.
How to solve this problem? There are at least two solutions, which are the next hungry singleton class and lazy singleton class.
1. Hungry singleton
The hungry Han type singleton class is the simplest singleton class to implement.
Define a static variable and instantiate the singleton class at the time of definition, so that the singleton object has been created when the class is loaded.
code:
/// <summary> ///Hungry Han style single case /// </summary> public class EagerSingleton { //Define static variables and instantiate singleton classes private static readonly EagerSingleton instance = new EagerSingleton(); //private constructors private EagerSingleton() { } //Get singleton object public static EagerSingleton GetInstance() { return instance; } }
If you use starving singleton to implement the design of load balancer, you will not create multiple singleton objects, which can ensure the uniqueness of singleton objects.
2. Lazy singleton class and thread locking
In addition to the hungry man singleton, there is also a classic lazy man singleton, which is the implementation of the load balancer mentioned earlier.
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 known as lazy load technology, that is, they are loaded when necessary.
In order to avoid multiple threads calling GetInstance() method at the same time, Lock can be used in C# to Lock threads
/// <summary> ///Lazy singleton /// </summary> public class LazySingleton { //Private static member variables that hold unique instances private static LazySingleton instance = null; private static readonly object syncLocker = new object(); private LazySingleton() {} //Public static member method, return unique instance public static LazySingleton GetInstance() { if (instance == null) { //Lock code block lock (syncLocker) { instance = new LazySingleton(); } } return instance; } }
The problem seems to have been solved, but the fact is not the same. If you use the above code to create a singleton object, there will still be the problem that the singleton object is not unique. The reasons are as follows:
If thread A and thread B are calling GetInstance() method at A certain moment, then the instance object is null, which can be judged by "instance==null". Due to the implementation of the locking mechanism, thread A enters the locked code block to execute the instance creation code. At this time, thread B is in the queue waiting state and can enter the lock code block only after thread A completes execution. However, after thread A completes execution, thread B does not know that the instance has been created, so it will continue to create A new instance, which will lead to multiple singleton objects, which is contrary to the design idea of singleton mode. Therefore, it needs to be further improved. You need to judge "instance==null" again in the locked code block to judge whether other threads have created singleton classes after entering the locked code block. This method is called double check locking. The code is as follows:
/// <summary> ///Lazy singleton /// </summary> public class LazySingleton { //Private static member variables that hold unique instances private static LazySingleton instance = null; private static readonly object syncLocker = new object(); private LazySingleton() {} /// <summary> ///Public static member method, return unique instance /// </summary> /// <returns></returns> public static LazySingleton GetInstance() { //First interpretation if (instance == null) { //Lock code fast lock (syncLocker) { //Second judgment if (instance == null) instance = new LazySingleton(); } } return instance; } }
3. Comparison between starving type and lazy type
Hungry Chinese singleton class: instantiates itself when the class is loaded.
Benefits:
- There is no need to consider the access problem of multithreading, which can ensure the uniqueness of the instance.
- Since the singleton object is created at the beginning, there is no need to wait in the call speed and reaction time, which is better than lazy.
Disadvantages:
- No matter whether the system needs to use the singleton object at runtime, it is created at the beginning. If the singleton object is only used in a certain place, creating the singleton object at the beginning will cause a waste of resources.
- If the instantiation of a singleton class takes a long time and is not used when the program is running, it will increase the unnecessary loading time of the system.
Lazy singleton class: created when the class is first used.
Benefits:
- There is no need to occupy system resources all the time, and the delayed loading is realized.
Disadvantages:
- When multiple threads access at the same time, if the instantiation of a singleton class is time-consuming, the probability of multiple threads referencing this class for the first time will increase, and each thread needs to go through the double check locking mechanism, which will have an impact on the performance of the system.
5, A better single instance implementation method
Hungry Chinese singleton class can not realize delayed loading. It always occupies memory whether it is used or not in the future; Lazy singleton thread safety control is cumbersome, and the performance is affected. Is there a way to overcome the shortcomings of both methods at the same time? have That is the static inner class singleton.
You need to add a static inner class to the singleton class. Create a singleton object inside the class, and then return the singleton object to external use through GetInstance() method. The code is as follows:
/// <summary> ///Static internal class, singleton, thread safety /// </summary> public class StaticSingleton { //Private constructor to prevent instantiation from the outside private StaticSingleton(){} //Public static member method, return unique instance public static StaticSingleton GetInstance() { return InnerClass.instance; } //Internal class. The InnerClass is loaded when GetInstance() is called for the first time class InnerClass { //Called when a class is instantiated or a static member is called //Here, when instance is called, it will execute static functions to initialize member variables static InnerClass(){} internal static readonly StaticSingleton instance = new StaticSingleton(); } }
Instance is not directly instantiated as a member variable of StaticSingleton, so StaticSingleton will not be instantiated when the class is loaded. When the getInstance () method is called for the first time, the inner class InnerClass will be loaded. The inner class defines a variable instance of static type. At this time, the member variable will be initialized first NET framework to ensure thread safety and ensure that the member variable can only be initialized once. Since GetInstance() is not locked by any thread, there is no performance impact.
Static constructor:
- It's by Net framework
- There are no parameters, because the framework doesn't know what parameters we want to pass
- It must be identified with static, and there are no public and private
- Instance variables cannot be initialized in a static constructor
- The static constructor is called when the class is instantiated or the static member is called, and is called by NET framework to call the static constructor to initialize the static member variable
- There can only be one static constructor in a class
- Parameterless static constructors and parameterless constructors can exist together
- The static constructor is executed only once
6, Summary of single case mode
As a design pattern with clear objectives, simple structure and easy understanding, singleton pattern is used very frequently in software development and is widely used in many software and frameworks.
1. Main advantages
- Singleton mode provides controlled access to unique instances. Because a singleton class encapsulates its unique instance, it can strictly control how and when customers access it.
- There is only one object in the system, so system resources can be saved. For those objects that need to be created and destroyed frequently, singleton mode can undoubtedly improve the performance of the system.
- Variable instances are allowed. Based on the singleton mode, developers can expand and use the method similar to that of controlling singleton objects to obtain a specified number of instance objects, which not only saves system resources, but also solves the problem that too many singleton objects are shared, which is detrimental to performance. (Note: classes that provide the execution number of instance objects by themselves can be called multi instance classes), such as database connection pool, thread pool, etc
2. Main disadvantages
- Since there is no abstraction layer in the singleton pattern, it is very difficult to extend the singleton class
- The responsibility of singleton class is too heavy, which violates the principle of single responsibility to a certain extent. Because the singleton class provides both business methods and methods for creating objects (factory methods), coupling the creation of objects with the functions of the objects themselves.
- Many object-oriented language (Java, C#) running environments now 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 shared singleton object state.
3. Applicable scenarios
- The system only needs one instance object. For example, the system needs to provide a unique serial number generator or resource manager, or it needs to consider that the resource consumption is too large to allow the creation of only one object.
- Only one public access point is allowed for a single instance of a client calling class. The instance cannot be accessed except through the public path
If you think this article is helpful to you, you are welcome to recommend it. Official account is also welcome.
Example code:
https://github.com/crazyliuxp/DesignPattern.Simples.CSharp