- The singleton pattern
- Thread-safe Singleton
- It would ruin Singleton’s situation
- Thread the Singleton
The singleton pattern is the most independent of several creation patterns, and its primary goal is not to generate a new instance from client calls, but to limit the number of instances of a type to one. GOF describes singletons as: Ensure a class only has one instance, and provide aglobal points of access to. — Design Patterns: Elements of Reusable Object-Oriented Software
The singleton pattern
There is no need to describe the application scenarios of the singleton pattern, but the simplest implementation is as follows:
public class Singleton
{
private Singleton() { }
private static Singleton instance;
public static Singleton Instance()
{
if (instance == null)
{
instance = new Singleton();
}
returninstance; }}Copy the code
This is done in Lazy mode, or you can initialize the instance directly when the static variable is created. This code already meets the design requirements of the original Singleton pattern and works fine in most cases. When multiple threads call the Instance static property of a Singleton class at almost the same time, the Instance member may not be instantiated yet, so it is created multiple times, and the last Instance is saved in the Singleton class. Each thread references different objects.
Thread-safe Singleton
In order to ensure that there is only one instance in multi-threaded environment, the code is optimized:
public class Singleton
{
private static volatile Singleton instance;
public static Singleton Instance()
{
if (instance == null)
{
lock (typeof(Singleton))
{
if(instance == null) { instance = new Singleton(); }}}returninstance; }}Copy the code
Here are some changes from the initial implementation:
- Instance is decorated with the volatile keyword, which indicates that a field may be modified by multiple threads executing concurrently.
- Locking the Singleton type before instantiation avoids the problem of multiple threads instantiating simultaneously.
- The first if is added before the lock to avoid the loss of efficiency associated with locking the Singleton type on every call.
- If instance is empty again after lock, it is possible for another thread to enter the first layer if while the first thread is locked and instantiated, so that if it is not nulled again, the instantiation will be repeated.
It would ruin Singleton’s situation
There are cases where the encapsulation of Singleton is broken and the “one instance only” restriction is skipped, which should be avoided in practice.
-
The first is to implement the ICloneable interface or to inherit from a related subclass so that the client can skip the hidden constructor with the ICloneable interface
-
New objects can also be generated by serialization or deserialization of binary, Json, etc.
Thread the Singleton
The previous discussion was about thread-safe Singleton implementations, but sometimes finer grained singletons are needed, such as thread-level singletons, as long as there is only one instance within a thread. This is similar to the AddScope registration provided by IOC in Asp.NET Core. You can guarantee that there is only one instance in an HttpContext.
Asp.NET Core provides a similar off-the-shelf implementation, but what if you also need thread-level instance control in a non-Web environment? Combined with c # provides System. ThreadStaticAttribute can complete
Through System. ThreadStaticAttribute a static variable can be limited to only within this thread is static. The implementation is as follows:
public class ThreadSingleton
{
private ThreadSingleton() {} [ThreadStatic] // Private static ThreadSingleton instance; public static ThreadSingletonInstance()
{
if (instance == null)
{
instance = new ThreadSingleton();
}
returninstance; }}Copy the code
Thread locks are no longer needed here, because thread-level singletons don’t need to worry about thread safety. To verify the accuracy of the implementation, first construct a target object for execution in a thread:
class Work { public static IList<int> Log = new List<int>(); /// </summary> // the execution part of each thread /// </summary> public voidProcedure() { ThreadSingleton s1 = ThreadSingleton.Instance(); ThreadSingleton s2 = ThreadSingleton.Instance(); Assert.IsNotNull(s1); Assert.IsNotNull(s2); Assert.areequal (s1.gethashCode (), s2.gethashCode ())); // Log HashCode log.add (s1.gethashCode ()); }}Copy the code
This class will execute within each thread, verify that Instance fetched multiple times within the thread is the same Instance, and record the Instance’s HashCode for comparison with other thread instances. Next open the Procedure() method for multiple threads simultaneously:
[Test]
public void ThreadSingletonTest() { int threadCount = 4; Thread[] threads = new Thread[threadCount]; // Create 4 threadsfor(int i = 0; i < threadCount; i++) { ThreadStart work = new ThreadStart(new Work().Procedure); threads[i] = new Thread(work); } // Execute thread foreach (var threadin threads)
{
thread.Start();
}
Thread.Sleep(10000);
Assert.AreEqual(threadCount, Work.Log.Distinct().Count());
}
Copy the code
The static variable Log of the Work class records the hashcodes of instances in each thread, which are different from each other and consistent with the number of threads, proving that instances are different from one thread to another.
Reference book: design patterns — implementation and extension of engineering based on C# by wang xiang