GOF Series: Episode #3 [The Singleton - aka the devils work]
Next in the series we continue with the creational group of patterns and move onto the singleton pattern.
What is the singleton and how does it work?
- A globally available single instance of class, where only one single instance can exist at any given point in time.
- Maintains and instantiates itself across multiple threads at any given time.
- All public methods and members are accessed via the single instance.
- Constructed instances cannot be created outside of the singleton class. This is achieved by using a private constructor only accessible from the singleton itself.
Is the singleton the devils work?
- They promote tight coupling between objects and components – consumers know exactly where to get a copy of their collaborating classes.
- Remember that coupling is a measure of dependencies.
- Cannot be easily substituted ala dependency injection.
- Inherently lack scale.
- Difficult to unit test.
Singleton example in C#
Given this singleton below:
public sealed class Singleton { static Singleton _instance = null; static readonly object _padlock = new object(); static List<int> _ThreadSafeCounterList = new List<int>(); static readonly Guid _instanceId = Guid.NewGuid(); private Singleton() { } public static Singleton Instance { get { lock (_padlock) { if (_instance == null) { _instance = new Singleton(); } return _instance; } } } public List<int> ThreadSafeCounterList { get { lock (_padlock) { return Singleton._ThreadSafeCounterList; } } set { lock (_padlock) { Singleton._ThreadSafeCounterList = value; } } } public Guid ID { get { return _instanceId; } } }
and this program:
class Program { static AutoResetEvent evt; static void Main(string[] args) { Console.WriteLine(“Enter a key to begin the singleton workers”); Console.ReadLine(); const int countup = 2000; for (int x = 0; x <= countup; x++) { //new up a background worker BackgroundWorker bg = new BackgroundWorker(); //wire up the work handler delegate to do the computation bg.DoWork += new DoWorkEventHandler(bg_DoWork); //when x has reach the upper boundary of the loop //subscribe to the event that fires when the //work handler has completed its work (will subscribe) //to the last thread dialled up via the bg worker if (x == countup) { //last bg worker thread - subscribed to its //completed event to signal an end bg.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bg_RunWorkerCompleted); } //run up a thread bg.RunWorkerAsync(x); } //wait until blocks the current thread (main thread) //until signaled - will pause here.. evt = new AutoResetEvent(false); evt.WaitOne(); } static void bg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { Console.WriteLine(“Start reading of the list in “ + “the order the items were written.”); Console.ReadLine(); //loop the list of integers and print //them in order they were inserted for (int i = 0; i < Singleton.Instance. ThreadSafeCounterList.Count; i++) { Console.WriteLine(Singleton.Instance. ThreadSafeCounterList[i].ToString()); } //signal to the user the total processing figures Console.WriteLine(“TOTAL added to Thread safe List is : “ + Singleton.Instance.ThreadSafeCounterList.Count.ToString()); //pause for user entry Console.ReadLine(); //signal to the main thread to continue //and therefore exit. evt.Set(); } static void bg_DoWork(object sender, DoWorkEventArgs e) { System.Threading.Thread.Sleep(10); Console.WriteLine(“Added “ + e.Argument.ToString() + ” by thread with hashcode “ + Thread.CurrentThread.GetHashCode() + “\tto instance ID:\t” + Singleton.Instance.ID.ToString()); //add the integer to the list Singleton.Instance.ThreadSafeCounterList.Add((int)e.Argument); //give back a time slice so the other threads get a piece System.Threading.Thread.Sleep(10); //write out to the window when you find //the same integer that was just added //because a time slice was given back to //the OS task scheduler, another thread //may be processing, so the found integer //and thread hashcode will not necessarily //print in order. Console.WriteLine(“Found “ + Singleton.Instance. ThreadSafeCounterList.Find( delegate(int findInt) { if (findInt == (int)e.Argument) { return true; } else { return false; } }).ToString() + ” by thread with hashcode “ + Thread.CurrentThread.GetHashCode() + “\tto instance ID:\t” + Singleton.Instance.ID.ToString()); } }
We have a program that will count up to 2,000 and use a thread safe singleton to insert and read those 2,000 counters. Each iteration will fire off a new background worker thread to do the work of both the insert and read of each of the iterations counter. Whilst this program serves no useful function, it represents an example of a thread safe singleton where data is shared and accessed in both read and volatile writes concurrently.
Next up
Next up in the series will the prototype pattern and I am going to try and include it with an IronRuby example as well as in C#. The entire Patterns series code and PowerPoint presentation can be downloaded here.
No comments









