Академический Документы
Профессиональный Документы
Культура Документы
NET
All About Singleton Pattern
2013-07-20 09:07:15 Jinal Desai
UML Diagram of Singleton Pattern The GetInstance() method is responsible for returning single instance of the class. If the static member is null it will instantiate the static member. The instantiation is done inside the GetInstance() method thus it needs to be thread safe. //sealed to make sure it's not inheritable public sealed class singleton {
//static member to ensure single placeholder for object throughout scope of the class private static singleton instance; //used for locking block of code private static readonly object locker = new object(); //private constructor to restrict direct instantiation of the class private singleton() { } public static singleton GetInstance() { //synchronization lock, only one thread will enter the block lock(locker) { if(instance == null) instance = new singleton(); } return instance; } /* public void OtherMethods() { } */ }
Examples
Load Balancer Classes Configuration Classes Common Shared Resource Classes (printer class, scanner class, etc) Logger Classes (logging framework) Factories (session factories) DBMS classes
Implementation Scenarios
Multi-threading Implementation : In multi-threaded usage, multiple threads are simultaneously accessing singleton class. If the instantiation is not thread-safe then there might be the chances that two threads holding difference instance object for same singleton class, which will spoil the purpose of singleton pattern. So, while implementing singleton pattern it is common practice to make the object instantiation logic thread-safe. Previous example shows basic thread-safe implementation in csharp. Protected Constructor : Normally, the constructor of a singleton class implementation is private. But in
scenario where subclassing of a singleton class is needed we can keep constructor of a singleton class as private. There are drawbacks associated with this kind of implementation. Protected constructor means, the class can be instantiated through calling the constructor from another class within the same namespace. Solution to the problem is creating separate namespace for singleton class implementation purpose. For using derived class, we need to change GetInstance calls from Singleton.GetInstance() to NewSingleton.GetInstance(). Implementation Using Static Field (Early Instantiation) : In previous code example, singleton class is instantiated in synchronized block only when it calls GetInstance() for the first time. If we need to ensure instantiation when the class is loaded, not when it is called first time GetInstance() method (called Early Instantiated) then we can achieve this using static fields instantiated directly which declared. //sealed to make sure it's not inheritable public sealed class singleton { //static member to ensure single placeholder for object throughout scope of the class //Early Instantiation private static singleton instance = GetInstance(); //private constructor to restrict direct instantiation of the class private singleton() { } //no need of synchronized block to make it thread safe public static singleton GetInstance() { if(instance == null) instance = new singleton(); return instance; } /* public void OtherMethods() { } */ } Double Locking Implementation (Lazy Instantiation) : The idea behind the double is to avoid costly synchronization for every invocations of the GetInstance() method. The cost of synchronization differs from compiler to compiler and even as the compilers are evolved the cost of synchronization decreases. But, it still affects performance, it still costs performance. So application developers never want to waste processing time whenever possible. Double locking is implementation for the same. //sealed to make sure it's not inheritable public sealed class singleton { //static member to ensure single placeholder for object throughout scope of the class private static singleton instance; //used for locking block of code private static readonly object locker = new object(); //private constructor to restrict direct instantiation of the class private singleton() { } //synchronized block needed if instance is null public static singleton GetInstance() { if(instance == null) {
//synchronization lock, only one thread will enter the block lock(locker) { //double checking for nullability of instance object if(instance == null) instance = new singleton(); } } return instance; } /* public void OtherMethods() { } */ } Now lets dry run the double locking algorithm. Step 1. First thread enters the GetInstance() method. Step 2. First thread enters the synchronized block because instance is null. Step 3. Now second thread comes into the picture. Second thread enters the GetInstance() method. Step 4. Second thread tries to acquire the lock, but since the first thread holds the lock second thread waits. Step 5. First thread continues execution. The instance is null so it creates a singleton object and assigns to instance variable. Step 6. First thread exits the synchronization block and returns instance from the GetInstance() method. Step 7. Since synchronized block is no longer blocked by any other thread second thread continues execution. Step 8. Second thread acquires the synchronization lock and checks whether instance is null or not. Step 9. The instance is not null so rather than creating second instance created it will return the instance already created by first thread. (In single locking case second thread at Step 8. will found instance null and create another instance, which would break singleton pattern) So, this is how double locking implementation works. Out Of Order Writes Final scenario we discuss is out of order writes. It is basically dealt with the synchronization between object instantiation and calling of construction of singleton class to make instantiation complete. Lets again dry run double locking algorithm with some different aspect in mind. Step 1: First thread enters GetInstance() method and found instance is null. Step 2: First thread enters synchronization block and found again instance is null. Step 3: Since first thread founds instance null inside the synchronization block it will instantiate instance object, but still the constructor of Singleton class is not executed. Step 4: Second thread comes into the picture, and checks if instance is null. Step 5: Since instance is already instantiated by first thread second thread found instance not null. Step 6: So, second thread will return instance object instantiated by first thread but it is partially initialized Singleton object. Step 7: First thread continues execution by completing initialization of the Singleton object by executing its constructor and returns with instance object. So, in above dry run double locking mechanism breaks. The situation here we generated by above dry run is called out-of-order writes. To avoid out-of-order write problem we need to introduce local variable instance1 and second synchronization block. Sample code is shown below. //sealed to make sure it's not inheritable public sealed class singleton { //static member to ensure single placeholder for object throughout scope of the class
private static singleton instance; //used for locking first synchronization block of code private static readonly object locker = new object(); //used for locking second synchronization block of code private static readonly object locker1 = new object(); //private constructor to restrict direct instantiation of the class private singleton() { } //synchronized block needed if instance is null public static singleton GetInstance() { if(instance == null) { //first synchronization lock, only one thread will enter the block lock(locker) { Singleton instance1 = instance; if(instance1 == null) { //second level synchronization lock, only one thread will enter the block lock(locker1) { instance1 = new singleton(); } instance = instance1; } } } return instance; } /* public void OtherMethods() { } */ } Lets dry run above algorithm to check whether it addresses out-of-order write scenario or not. Step 1: First thread enters GetInstance() method. Step 2: The instance object is null, so it enters the first synchronization block and assign instance1 the value of instance which is null right now. Step 3: First thread now checks the value of instance1 which is null so enters second synchronization block and instantiate instance1 but constructor execution is still pending execution. Step 4: Now, second thread comes into the picture. Second thread enters into the GetInstance method and checks whether instance is null or not. Step 5: Since instance is still null (first thread still not assign initialized object to instance, its still reference initialized object to instance1), second thread tries to enter first synchronization block but since first thread is holding the lock second thread not able to enter first synchronization block. Step 6: First thread completes execution by assigning fully constructed object instance1 to instance and leaves both synchronization blocks and returns fully constructed instance object. Step 7: Second thread will get the access to first synchronization block and assigns instance object reference which is now not null to instance1. Step 8: Now, while checking not null of instance1, second thread will found its not null and already initialized. So, second thread will not enter the second synchronization block. Step 9: Second thread returns fully constructed instance object. There are some optimizations also possible to the above algorithm, but this is the basic algorithm which addresses both double-checked locking scenario as well as out-of-order writes scenario. References