Freigeben über


Know your design tools — The Singleton case

A professional software designer —one whose next paycheck depends on the quality of her software— looks for an ever increasing acquaintance with his design tools.

One of the most important design tools in software is the actual computing machine —abstract, virtual and physical— where the software under design will be executed. The reason why the computer is a design tool is the interacting or conversational exchange with it that allows better design decisions. That is, the practice of a reflective conversation with the situation at hand as described by The Reflective Practitioner: How Professionals Think in Action by Donald A. Schön.

One benefit out of such conversational exchange with the computing environment is that hardware and software components will be utilized closer to their original intent, unnecessary code will not be present and thus simple, clean, less complex, and more maintainable software could be put on the hands of users.

For instance, if you need to implement the Singleton design pattern using Microsoft Visual C# upon the Common Language Infrastructure (CLI) implementation from Microsoft, the Common Language Runtime (CLR), then it is better to first know these design tools in depth instead of trying ‘as is’, the same, unquestioned assumptions from other computing environments.

The point of Singleton is to have a single instance of a class, an exact same object shared across a given CLR AppDomain inside a Windows’s Win32_Process instance, that is, a global state available to all executing threads in the give CLR AppDomain.

The idea is, given this method...

 static bool AreTheseTheExactSameObject(object a, object b)
{
  return object.ReferenceEquals(a, b);
}

...that the following assertion will succeed, even if this assertion is executed concurrently by a plethora of AppDomain’s threads:

 object a = TimepointSingleton.Instance;
object b = TimepointSingleton.Instance;
System.Diagnostics.Debug.Assert(AreTheseTheExactSameObject(a, b));

The horror of global variables from old structured design techniques strikes back, now in the form of nice-looking object oriented terms. The horror could be kept in control if the shared state is read-only. But, even if the shared object’s state is read-only, there is one state that must be written once at instantiation time: the variable holding the reference to that single and shared object.

At instantiation time the following constructor must be executed only once:

 private TimepointSingleton()
{
  point = DateTime.Now;
  Console.WriteLine("Timepoint acquired");
  
  //This simulates resource contention to show up
  //better the multithreading problem when no
  //proper thread synchronization is in place.
  System.Threading.Thread.Sleep(4000);
}

With the following class type member field:

 private DateTime point;

The private access modifier in the constructor ensures that instantiation could only occur inside the class; no other place in the program could create objects of this class. The one and only instance of this class is created by the class itself and its reference will be kept in a private static field member and accessed by a static read-only property:

 private static readonly TimepointSingleton instance;
public static TimepointSingleton Instance { get { return instance; } }

Of course, the heart of the issue is that multiple threads could invoke that public static read-only property simultaneously; which is not a problem once the object reference is in place —in the reference type variable of the private static field member— because, once initialized, this field’s value (an object reference) is read-only state for those multiple threads.

So the main design decision here is how to write the object reference of the one and only instance into that private static field.

Modern computing environments usually have implicit and explicit mechanisms that ensure the execution of a single thread in a given code point. The implicit ones are provided by the underlying execution environment and are the simplest and easy to use. That kind of mechanism is what we prefer here over explicit mechanisms that could convolute and add complexity to our design unnecessarily. Visual C++ with native code has them, and also Visual C# with the current CLR.

Explicit mechanisms for thread synchronization at initialization time involving single or double checks and volatile fields could be applied successfully, but if simpler, implicit, mechanisms are at hand that could deliver clearer results, then they should be chosen.

Based on empirical analysis and also corroborated from CLR via C# by Jeffrey Richter, that simpler mechanism for the Singleton case is a static constructor, (also named type initializer or class constructor).
From the Microsoft Visual C# Language Specification 3.0, in the 10.12 Static constructors section:

The static constructor for a closed class type executes at most once in a given application domain.

So, the crucial code would be:

 static TimepointSingleton()
{
  instance = new TimepointSingleton();
}

This way, the class is not marked with the beforefieldinit CLR metadata attribute that allows the CLR to optimize type initialization for perfomance, and not necessarily for correctness. So the following field initializer accomplishes not exactly the same thing (that is, the beforefieldinit attribute is still generated):

 private static readonly TimepointSingleton instance = new TimepointSingleton();

For additional perspectives click here.

Comments

  • Anonymous
    March 29, 2009
    PingBack from http://blog.a-foton.ru/index.php/2009/03/30/know-your-design-tools-%e2%80%94-the-singleton-case/

  • Anonymous
    March 30, 2009
    Interesting Finds: March 30, 3009

  • Anonymous
    March 30, 2009
    Very ilustrative sample of how to implement the design pattern. It´s very common to find samples and implementations in other languages (included .Net),but we must consider that each has its own runtime implications.

  • Anonymous
    March 31, 2009
    El acto de concebir, diseñar y codificar software demanda ciertas conductas en quien lo ejecuta que han