Singleton is one of the easiest design patterns to implement. Here I am discussing about the overhead we face with lock objects when we have to support Singleton in a multi-threaded environment.
[For the easiness of expressing, I prefer C# over C++.]
class FileSystem
{
public static FileSystem getInstance()
{
if( instace == null )
instace = new FileSystem();
return instace;
}
private static FileSystem instace;
}
The above is a simple singleton class. But when the the instance is being accessed from multiple thread, we will have to employ some synchronization mechanisms to avoid the issues.
class FileSystem
{
public static FileSystem getInstance()
{
lock (lockObj)
{
if( instace == null )
{
instace = new FileSystem();
}
return instace;
}
}
private FileSystem()
{
}
private static FileSystem instace;
private static object lockObj = new object();
}
Yes we’ve synchronized it better. Now what’s the issue, the instance creation is synchronized but this is a single time activity. i.e the instance is created only once by either of the threads. During the life time of the program, the lock which is employed is not really necessary. Each time a thread call FileSystem.getInstance(); it enters the costly lock(lockObj); statement rather than returning the already allocated object.
How to sort this issue? Double null checking is a famous technique to avoid the overheads caused by the locks for already allocated instance objects.
See a sample implementation
namespace DoubleNullCheck
{
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 10; i++)
{
Thread t = new Thread(Program.doWork);
t.Start();
}
Thread.Sleep(2000);
bStop = true;
Thread.Sleep(1000);
Console.WriteLine("Exiting...");
}
static void doWork()
{
while (!Program.bStop)
{
FileSystem fs = FileSystem.getInstance();
}
}
public static volatile bool bStop = false;
}
// Singleton class
class FileSystem
{
// getInstance with double null check
public static FileSystem getInstance()
{
if( instace == null )
{
Console.WriteLine("1 depth null check" + Thread.CurrentThread.ManagedThreadId);
lock (lockObj)
{
Console.WriteLine("entered lock" + Thread.CurrentThread.ManagedThreadId);
if (instace == null)
{
Console.WriteLine("2 - Newing the object" + Thread.CurrentThread.ManagedThreadId);
instace = new FileSystem();
}
else
Console.WriteLine("2 - Ohh thread is already allocated" + Thread.CurrentThread.ManagedThreadId);
}
}
return instace;
}
private FileSystem()
{
}
private static FileSystem instace;
private static object lockObj = new object();
}
}
What happens here?
Thread 1 calls the getInstance() method and determines that instance is null at //1st null check
Thread 1 enters the if block, but is preempted by thread 2
Thread 2 calls the getInstance() method and determines that instance is null at //1st null check
Thread 2 enters the if block and creates a new Singleton object
Thread 2 returns the Singleton object reference
Thread 2 is preempted by thread 1.
Thread 1 continue it's execution and realize that, the object is alrady created (at 2nd null check) and returns
The subsequent calls to getInstance from any threads simply returns the instance after null check
Thus we can improve the overhead of unnecessary lock checking!