The Singleton Pattern is a strategy that provides a single instance of a certain class for an entire application ( strictly speaking in .NET it's per Application Domain ); and ensures that any reference to an instance of that class in that application is to that single instance. This approach can help maintain consistency in multithreaded scenarios and simplify resource sharing while requiring a minimum of not-very-object-oriented
This is one of the Design Patterns attributed to the Gang of Four.
static
-heavy code.This is one of the Design Patterns attributed to the Gang of Four.
This pattern is typically implemented using lazy instantiation - only creating the instance the first time it is referenced - which can be achieved using double-checked locking to minimise the performance overhead of thread synchronisation, as follows:
sealed class SingletonOne {
private SingletonOne() {
// constructor logic
}
private static SingletonOne s_instance;
private static object s_padlock = new Object();
public static SingletonOne Instance {
get {
if ( s_instance == null ) {
lock( s_padlock ) {
if ( s_instance == null ) {
s_instance = new SingletonOne();
}
}
}
return s_instance;
}
}
}
There is nothing actually wrong with this ( as opposed to the examples you see that implement
no thread-synchronisation at all ) but it is more handmade and marginally less efficient than the following implementation which takes advantage of some built-in CLR features:
What this second implementation gives you is a single instance that is thread-safely single ( ensured by the CLR's initialisation of static fields and enforcement of the
no thread-synchronisation at all ) but it is more handmade and marginally less efficient than the following implementation which takes advantage of some built-in CLR features:
sealed class SingletonTwo {
private SingletonTwo() {
// constructor logic
}
public static readonly SingletonTwo Instance = new SingletonTwo();
}
What this second implementation gives you is a single instance that is thread-safely single ( ensured by the CLR's initialisation of static fields and enforcement of the
readonly
attribute ) but without the overhead of the null
check or even the accessor call - this field access is about 20 times faster than the above property access ( see complete program at the end of the post ); as well as far less code to write, maintain, curse etc.One thing to be aware of, however, is the timing of this new initialisation scheme. Disassembling the program with
The point of interest here being the
ILDasm.exe
shows the class declaration as follows:.class private auto ansi sealed beforefieldinit SingletonTwo
extends [mscorlib]System.Object
The point of interest here being the
beforefieldinit
attribute - this tells the CLR to ensure that the field is initialised at some point before the field is accessed; in other words, it could occur quite a while before - at any point when the CLR deems appropriate based on factors such as CPU load ( there's a very good article on this here: http://csharpindepth.com/Articles/General/Beforefieldinit.aspx ).If the timing of a singleton's initialisation code is important and it should run just before first access, not at some arbitrary point earlier on; this can be achieved by instructing the compiler to omit the
This class definition disassembles to the following:
( note the lack of
beforefieldinit
attribute, which in C# is as simple as defining a class constructor ( sometimes called a static constructor ). The following class definition shows this in action:sealed class SingletonThree {
private SingletonThree() {
// constructor logic
}
public static readonly SingletonThree Instance;
static SingletonThree() {
Instance = new SingletonThree();
}
}
This class definition disassembles to the following:
.class private auto ansi sealed SingletonThree
extends [mscorlib]System.Object
( note the lack of
beforefieldinit
; note also that it's irrelevant what the class constructor actually does - its presence signals the compiler to omit that attribute )So all in all: cleaner, quicker code and control over timing - what's not to like?
By the way, for my money the best reference of on MSIL internals such as
beforefieldinit
is Inside Microsoft .NET IL Assembler by Serge Lidin, the guy behind IL itself. Below is a complete program that includes the performance testing from which I arrived at the property access vs. field access figures:
using System;
using System.Diagnostics;
sealed class SingletonOne {
private SingletonOne() {
// constructor logic
}
private static SingletonOne s_instance;
private static object s_padlock = new Object();
public static SingletonOne Instance {
get {
if ( s_instance == null ) {
lock( s_padlock ) {
if ( s_instance == null ) {
s_instance = new SingletonOne();
}
}
}
return s_instance;
}
}
}
sealed class SingletonTwo {
private SingletonTwo() {
// constructor logic
}
public static readonly SingletonTwo Instance = new SingletonTwo();
}
sealed class SingletonThree {
private SingletonThree() {
// constructor logic
}
public static readonly SingletonThree Instance;
static SingletonThree() {
Instance = new SingletonThree();
}
}
class test {
static void Main() {
Console.WriteLine(
"Testing SingletonOne: {0}", SingletonOne.Instance == SingletonOne.Instance
);
Console.WriteLine(
"Testing SingletonTwo: {0}", SingletonTwo.Instance == SingletonTwo.Instance
);
Console.WriteLine(
"Testing SingletonThree: {0}", SingletonThree.Instance == SingletonThree.Instance
);
// performance testing
const int ITERATIONS = 50000000;
// test property access
GC.Collect();
GC.WaitForPendingFinalizers();
Stopwatch timerOne = Stopwatch.StartNew();
for ( int i=0; i<ITERATIONS; i++ ) {
SingletonOne s = SingletonOne.Instance;
}
timerOne.Stop();
// test field access
GC.Collect();
GC.WaitForPendingFinalizers();
Stopwatch timerTwo = Stopwatch.StartNew();
for ( int i=0; i<ITERATIONS; i++ ) {
SingletonTwo s = SingletonTwo.Instance;
}
timerTwo.Stop();
Console.WriteLine(
"Property access: {0} ms", timerOne.ElapsedMilliseconds / ( double ) ITERATIONS
);
Console.WriteLine(
"Field access: {0} ms", timerTwo.ElapsedMilliseconds / ( double ) ITERATIONS
);
}
}
No comments:
Post a Comment