This is the third day of my participation in the August More text Challenge. For details, see:August is more challenging
The singleton pattern is the simplest of all the design patterns, and it is one that we often use in general. The singleton pattern is usually applied to thread pools, cache operations, queue operations, and so on.
The purpose of a singleton pattern is to create an instance of a class. To create an instance of a class, we can do that with a global static variable or convention. Why do we use a singleton pattern?
Next we will explain how to form a singleton pattern and how to create a singleton pattern.
1. How are singletons formed
We usually create an object and we need a new object, so let’s say we have an object ObjectClass and we instantiate it.
new ObjectClass()
Copy the code
If another class wants to use ObjectClass then we can create another instantiation using new. If the class is public then we can instantiate the object multiple times as we use it.
So how can we ensure that the class is not instantiated by another class? With the private keyword we can use private constructors to prevent the class from being instantiated externally.
public class ObjectClass
{
private ObjectClass()
{
}
}
Copy the code
So we can’t instantiate ObjectClass and we can’t use it. So how do we instantiate it?
Since we can only access private constructors internally, we can instantiate ObjectClass with an internal method that we set to static in order for the method to be accessed externally.
When we do this, we make sure that the object we return is always the one we first created. We use a private static object to store the instantiated object. If the object is not created, we create it immediately, and if it is created, we return the created object.
public class ObjectClass { private static ObjectClass singleton; private ObjectClass() { } public static ObjectClass GetSingletone() { if (singleton == null) { singleton = new ObjectClass(); } return singleton; }}Copy the code
At this point our singleton pattern is formed. The singleton pattern definition is:
Singleton pattern: Ensures that there is only one instance of a class and provides a global point of access.
Multithreading causes singleton mode problems
Enable multithreaded test singletons to return objects
class Program { static void Main(string[] args) { for (int i = 0; i < 10; i++) { TestSingleton(); } Console.ReadKey(); } public static void TestSingleton() { Task.Factory.StartNew(new Action(() => { var hc = ObjectClass.GetSingletone().GetHashCode(); Console.WriteLine(hc); })); }}Copy the code
Like the test shown in the figure, I started 10 threads to get the singleton and then printed the HashCode for the object. The test found HashCode inconsistencies, proving that the singleton does not return only one object.
If (singleton == null) ¶ If (singleton == null) has not been instantiated, then multiple threads may enter the instantiation code and return different instantiated objects.
3. Solve multi-threaded singleton problems
Since multithreading causes the if check variable problem, there are two types of solutions to the contention check problem:
(1) “urgently” create instances without delaying instantiation
Urgent instantiation is to create the object in the static initializer, thus ensuring that the singleton is created at runtime, removing the if judgment.
public class ObjectClass { private static ObjectClass singleton=new ObjectClass(); private ObjectClass() { } public static ObjectClass GetSingletone() { return singleton; }}Copy the code
(2) lock
In order to create an object that can only be done by one thread, we lock the create object code and modify the GetSingletone method again.
public class ObjectClass { private static ObjectClass singleton = new ObjectClass(); private static object lockObj = new object(); private ObjectClass() { } public static ObjectClass GetSingletone() { lock (lockObj) { if (singleton == null) { singleton = new ObjectClass(); } } return singleton; }}Copy the code
There is a performance cost to locking. If your system has high performance requirements, there is another way to optimize locking: double check locking
public static ObjectClass GetSingletone() { if (singleton == null) { lock (lockObj) { if (singleton == null) { singleton = new ObjectClass(); } } } return singleton; }Copy the code
With double-checked locking, multiple threads can run without entering the lock section if they have created singletons, thereby reducing the performance cost of locking.
We then test another wave, enabling 50 threads, and can see that the output HashCode is consistent.
4, summarize
Going back to the reason we started with why we don’t use global variables or conventions to solve singletons, because there are conventions for our development but we can’t guarantee that everyone is following conventions or abusing global variables to cause problems.
Using singletons allows for better self-engagement and management. Of course, we can abuse singletons, which requires a deep understanding of what problems they solve and how to use them.
Design patterns are not meant to be applied mechanically, but to be used appropriately in situations where they are needed.
Although the singleton pattern is relatively simple, we have seen a lot of problems through our analysis. To use it better, we need to do a better analysis. I hope this blog post has been helpful to you.