Singleton Design Pattern
From JholJhapata
The singleton pattern is a software design pattern that restricts the instantiation of a class to one single instance. This is useful when exactly one object is needed to coordinate actions across the system.
Problem
If we have some resource that can only have a single instance and you need to manage that single instance that is where Singleton Pattern plays important role. For Example, logger classes , driver object classes etc.
Example
Let’s take a look at an example, which is creating multiple objects.
class Program
{
static void Main(string[] args)
{
SingleTon obj1 = new SingleTon();
obj1.printText("obj1");
SingleTon obj2 = new SingleTon();
obj2.printText("obj2");
Console.WriteLine("Main End");
Console.ReadLine();
}
}
class SingleTon
{
private static int objectCounter = 0;
public SingleTon()
{
objectCounter++;
Console.WriteLine("objectCounter : " + objectCounter.ToString());
}
public void printText(string strData)
{
Console.WriteLine(strData);
}
}
Output
objectCounter : 1 obj1 objectCounter : 2 obj2 Main End
We created 2 object for printing text log.
Solution
The common characteristics of a Singleton Pattern:
- A single constructor, that is private and parameter-less.
- The class is sealed.
- A static variable that holds a reference to the single created instance, if any.
- A public static means of getting the reference to the single created instance, creating one if necessary.
First version - not thread-safe
class Program
{
static void Main(string[] args)
{
SingleTon obj1 = SingleTon.getInstance;
obj1.printText("obj1");
SingleTon obj2 = SingleTon.getInstance;
obj2.printText("obj2");
Console.WriteLine("Main End");
Console.ReadLine();
}
}
class SingleTon
{
private static int objectCounter = 0;
private static SingleTon instance;
private SingleTon()
{
objectCounter++;
Console.WriteLine("objectCounter : " + objectCounter.ToString());
}
public static SingleTon getInstance
{
get
{
if(instance == null)
instance = new SingleTon();
return instance;
}
}
public void printText(string strData)
{
Console.WriteLine(strData);
}
}
Output
objectCounter : 1 obj1 obj2 Main End
Problem 1
Example
class Program
{
static void Main(string[] args)
{
System.Threading.Tasks.Parallel.Invoke(
() => obj1Fn(),
() => obj2Fn()
); ;
Console.WriteLine("Main End");
Console.ReadLine();
}
public static void obj1Fn()
{
SingleTon obj1 = SingleTon.getInstance;
obj1.printText("obj1");
}
public static void obj2Fn()
{
SingleTon obj2 = SingleTon.getInstance;
obj2.printText("obj2");
}
}
class SingleTon
{
private static int objectCounter = 0;
private static SingleTon instance;
private SingleTon()
{
objectCounter++;
Console.WriteLine("objectCounter : " + objectCounter.ToString());
}
public static SingleTon getInstance
{
get
{
if (instance == null)
instance = new SingleTon();
return instance;
}
}
public void printText(string strData)
{
Console.WriteLine(strData);
}
}
Output
objectCounter : 1 objectCounter : 2 obj1 obj2 Main End
Problem 2
Example
class Program
{
static void Main(string[] args)
{
SingleTon.SingleTonChild obj1 = new SingleTon.SingleTonChild();
obj1.printText("obj1");
SingleTon.SingleTonChild obj2 = new SingleTon.SingleTonChild();
obj2.printText("obj2");
Console.WriteLine("Main End");
Console.ReadLine();
}
}
class SingleTon
{
public class SingleTonChild: SingleTon
{
}
private static int objectCounter = 0;
private static SingleTon instance;
private static readonly object lockObj = new object();
private SingleTon()
{
objectCounter++;
Console.WriteLine("objectCounter : " + objectCounter.ToString());
}
public static SingleTon getInstance
{
get
{
if (instance == null)
{
lock (lockObj)
{
if (instance == null)
instance = new SingleTon();
}
}
return instance;
}
}
public void printText(string strData)
{
Console.WriteLine(strData);
}
}
Output
objectCounter : 1 obj1 objectCounter : 2 obj2 Main End
Second version - simple thread-safety and sealed
class Program
{
static void Main(string[] args)
{
System.Threading.Tasks.Parallel.Invoke(
() => obj1Fn(),
() => obj2Fn()
); ;
Console.WriteLine("Main End");
Console.ReadLine();
}
public static void obj1Fn()
{
SingleTon obj1 = SingleTon.getInstance;
obj1.printText("obj1");
}
public static void obj2Fn()
{
SingleTon obj2 = SingleTon.getInstance;
obj2.printText("obj2");
}
}
sealed class SingleTon
{
private static int objectCounter = 0;
private static SingleTon instance;
private static readonly object lockObj = new object();
private SingleTon()
{
objectCounter++;
Console.WriteLine("objectCounter : " + objectCounter.ToString());
}
public static SingleTon getInstance
{
get
{
lock (lockObj)
{
if (instance == null)
instance = new SingleTon();
}
return instance;
}
}
public void printText(string strData)
{
Console.WriteLine(strData);
}
}
Output
objectCounter : 1 obj1 obj2 Main End
Third version - attempted thread-safety using double-check locking
class Program
{
static void Main(string[] args)
{
System.Threading.Tasks.Parallel.Invoke(
() => obj1Fn(),
() => obj2Fn()
); ;
Console.WriteLine("Main End");
Console.ReadLine();
}
public static void obj1Fn()
{
SingleTon obj1 = SingleTon.getInstance;
obj1.printText("obj1");
}
public static void obj2Fn()
{
SingleTon obj2 = SingleTon.getInstance;
obj2.printText("obj2");
}
}
sealed class SingleTon
{
private static int objectCounter = 0;
private static SingleTon instance;
private static readonly object lockObj = new object();
private SingleTon()
{
objectCounter++;
Console.WriteLine("objectCounter : " + objectCounter.ToString());
}
public static SingleTon getInstance
{
get
{
if (instance == null)
{
lock (lockObj)
{
if (instance == null)
instance = new SingleTon();
}
}
return instance;
}
}
public void printText(string strData)
{
Console.WriteLine(strData);
}
}
Output
objectCounter : 1 obj1 obj2 Main End