What is singleton pattern?
The singleton pattern is the design of a class in such a way which restricts the creation of objects outside the class, but the same time comes up with a mean to render its instance when asked by the calling code. The same object is served for any requests.
Prerequisites
A singleton class should have only one instance, not more than one instance is created.
This can be accomplished by setting all constructors to be private so that new instance can not be created outside the class (for eg LogManager log=new LogManager() is not possible)
A singleton class should provide a global point of access its instance.
This is possible by creating a public static method and this method should return the instance reference of the class. The calling code would be like LogManager logObj=LogManager.GetInstance(). Notice the client code is not using “new” keword instead it uses the public static method “GetInstance” of the class LogManager
Uses
It saves memory because object is not created on each request because the calling code is not using the “new” keyword to create instance for this class.
It can be used in logging, caching, thread pools, configuration settings etc.
Different Versions
Many versions of single patterns are available, so choosing wisely based on your requirement is the key in implementing the singleton pattern. Typically the singleton class has only one instance. There could be any number of object references but only one instance of the class exists. The below diagram illustrates the basic idea of singleton
Standard Single Threaded Environment:
In this context, the application or system is running on one thread and you are very sure that not more than one thread access the Singleton class
public class Singleton
{
private static Singleton instance;
private Singleton() { }
public static Singleton GetInstance()
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
Program explanation
Concept explanation
Descriptive explanation
Static member:
It gets memory only once because of static and contains the instance of the Singleton class itself.
Private static:
It should be initialized and used only inside of the class
Private constructor:
It restricts the object creation from outside
Static factory method:
It provides the global point of access to the Singleton object and returns the instance of the singleton class. When this method is called, it checks if an instance of the singleton class is available. If not available, it creates an instance. Otherwise, it returns the same already created object reference of the class.
Lazy instantiation:
The object creation operation is deferred until the method is called first time by the calling code. This deferring approach is called lazy instantiation. It is generally used when improving performance, reducing database hits, optimized memory uses and minimizing the huge computation are primary considerations in designing or developing the systems
Disadvantages
There are some trade-offs in this approach. As long as we are sure that there is only one thread accessing singleton object, then we don’t have any issue. The above approach is enough. But in multi-threaded environment, there is a possibility that more than one thread can access the same line of code. Here comes the problem.
It is not safe for multithreaded environments. If two threads enter “if condition” if (instance == null) at the same time, then two instances of Singleton will be created.
Multithreaded Environment: Thread Safe
Approach 1:Early instance creation
The keyword “readonly” enforces that the object can be initialized either at the declaration part or at the construction level. This makes possible the early instance creation. So When the class is first compiled and loaded into AppDomain, that time itself one copy of Singeton memory is created in heap memory because the Singleton instance is declared as “static” and assigned the object reference using new Singleton() in declaration itself.
Approach 2: Double checked locking, lazy initialization
The keyword “lock” is the main player which holds lock on the code to block other threads to enter it the current thread finishes the execution and goes out of the locked block. In java, they call it as “synchronized ”. The keyword volatile ensures that assignment to the instance variable completes before the instance variable can be accessed. This is optional and it provides some extra assurance.
Real time example
using System.IO;
using System.Text;
namespace Utility
{
public class Logger
{
private FileStream fStream;
private StreamWriter sWriter;
private static readonly Logger _instance = new Logger();
public static Logger Instance
{
get
{
return _instance;
}
}
private Logger()
{
fStream = File.OpenWrite(@"c:\logs\log.txt");
sWriter = new StreamWriter(fStream);
}
public void LogMessage(string msg)
{
sWriter.WriteLine(msg + " logged at " + DateTime.UtcNow.ToString());
sWriter.Flush();
}
}
class Program
{
static void Main(string[] args)
{
string logMsg = "testing";
Console.WriteLine("log message :{0}", logMsg);
Logger.Instance.LogMessage(logMsg);
Console.WriteLine("Message logged successfully");
Console.ReadKey();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Multiton Pattern
Multiton pattern is the extended and enhanced version of Singleton pattern. As the name of the patterns says, the Multiton pattern is nothing but a predefined "n" of collection of instances of the class, while Singleton class has only one instance. The Multiton pattern(class) uses the Hash or Dictionary to group the list of instances. Each instance in the list is paired with respective key. By using key, the corresponding instance is returned to the calling code.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Utility
{
public sealed class BackendServer
{
private static readonly Dictionary<int, BackendServer> serverPool =
new Dictionary<int, BackendServer>
{
{1,new BackendServer() { ServerName="Server 1", IPAddress="121.121.121.121" } },
{2,new BackendServer() { ServerName="Server 2", IPAddress="121.125.129.122" } },
{3,new BackendServer() { ServerName="Server 3", IPAddress="121.131.121.123" } }
};
private static readonly object _lock = new object();
string ServerName { get; set; }
string IPAddress { get; set; }
public void Display()
{
Console.WriteLine("Request received by backend server{0}", ServerName);
}
private static readonly Random random = new Random();
private static readonly object randomLock = new object();
public static BackendServer GetAvailableBackendServer()
{
lock (randomLock)
{
int key = random.Next(1,(serverPool.Count+1));
return serverPool[key];
}
}
}
class LoadBalancer
{
BackendServer ConnectToAvailableServer()
{
return BackendServer.GetAvailableBackendServer();
}
public void SericeRequest()
{
BackendServer instance=ConnectToAvailableServer();
instance.Display();
}
}
class ClientProgram
{
static void Main(string[] args)
{
LoadBalancer reqObj = new LoadBalancer();
for(int i=1;i<=10;i++)
reqObj.SericeRequest();
Console.ReadKey();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64