Singleton is perceived as controversial design pattern, in some publications it is criticized and placed in a bucket along with antipatterns. Never less Singleton is used in variety of applications for a reason. Singleton helps to ensure a single instance of an object is created throughout application lifecycle.
In this post we are going to step back a little, clear our minds from all opinions and focus on practical aspects of Singleton Design pattern. We are going to expand on how it can be used safely to solve real world problems.
There are so many ways to implement Singleton
Singleton is quite simple concept to comprehend. In essence, it is a pattern that helps to 3nsure a single instance of a class exists throughout entire application lifecycle. However, despite its simplicity, there are so many ways to implement and use or even misuse the pattern. To help us choose the right Singleton implementation option, let’s look at different options and categorize them by the following dimensions:
- Mutability
- Singularity
- Instantiation Greediness
Singleton Mutability Dimension
Mutability dimension defines whether Singleton externally exposed state can be changed with its public API or remain unchanged throughout entire application lifecycle. We can define two mutability categories: Read Only and Mutable.
Read Only
After read only singleton is instantiated, it can never be changed from the outside. Even if Singleton internal state changes this fact stays invisible to other parts of an application and application’s behavior is not impacted.
Here is an example of read only application configuration singleton class that reads environment variables once and cached values.
public class MyAppConfiguration { /// <summary> /// Instantiates single instance of MyAppConfiguration. /// </summary> private static readonly MyAppConfiguration _instance = new MyAppConfiguration { ProxyHost = System.Environment.GetEnvironmentVariable("PROXY"), ProxyPort = int.Parse(System.Environment.GetEnvironmentVariable("PROXY")) }; /// <summary> /// Private constructor. /// Making constructor private blocks external code from creating /// new instances of MyAppConfiguration. /// </summary> private MyAppConfiguration() { } /// <summary> /// Returns single instance of MyAppConfiguration /// </summary> public static MyAppConfiguration Instance { get { return _instance; } } /// <summary> /// Returns proxy host. /// </summary> public string ProxyHost { get; private set; } /// <summary> /// Returns proxy port. /// </summary> public int ProxyPort { get; private set; } }
MyAppConfiguration can be safely used to read configuration values, but its state remains unchanged after instantiation.
public void UsingReadonlySingleton() { var proxyUri = $"{MyAppConfiguration.Instance.ProxyHost}:{MyAppConfiguration.Instance.ProxyPort}"; Console.Write($"Proxy address: {proxyUri}"); }
Mutable
Mutable Singleton allows its state to be changed by external classes after it was instantiated and the state mutations do impact application behavior.
The worst example is mutable Singleton that serves as global variable bag. In the example below GlobalVariableBag.GlobalVariable
can be modified by any class at any time which makes debugging and troubleshoot much harder.
public class GlobalVariableBag { /// <summary> /// Global variable that anyone can modify. /// </summary> public static string GlobalVariable { get; set; } }
Good example of mutable Singleton is Publisher class when we need to implement Publisher and Subscriber pattern. In the example below PubSubAsSingleton
class is singleton that implements PubSub pattern. LoadingEventArgs
class just defines event arguments.
/// <summary> /// Singleton that handles Publishers and Subscriber /// </summary> public class PubSubAsSingleton { private static readonly PubSubAsSingleton _instance = new PubSubAsSingleton(); private PubSubAsSingleton() { } public static PubSubAsSingleton Instance => _instance; /// <summary> /// Sunscribe if you would like to be notified about loading progress /// </summary> public event EventHandler<LoadingEventArgs> LoadingEvent; /// <summary> /// Use this mething if you would like to publish Loading events. /// </summary> /// <param name="sender">Identify who is the sender.</param> /// <param name="loadedProgress">Provide loading progress to sunscribers.</param> public void RaiseLoadingEvent(object sender, double loadedProgress) { var loadingEvent = LoadingEvent; loadingEvent?.Invoke(sender, new LoadingEventArgs(loadedProgress)); } } /// <summary> /// Class that implements LoadingEvent arguments. /// </summary> public class LoadingEventArgs { public LoadingEventArgs(double loadedProgress) { LoadedProgress = loadedProgress; } public double LoadedProgress { get; private set; } }
Here is how PubSubAsSingleton
can be used.
// Subscribing to Loading event PubSubAsSingleton.Instance.LoadingEvent += (sender, args) => { if (args.LoadedProgress >= 1) { Console.WriteLine($"Loaded!"); } else { Console.WriteLine($"Loading... {args.LoadedProgress * 100}%"); } }; // Publishing Loading events for (var i = 0; i <= 100; i++) { PubSubAsSingleton.Instance.RaiseLoadingEvent(this, i / 100.0); }
Singularity Dimension
Singularity dimension defines whether there is zero theoretical probability for more than one instance of singleton object to exist. Why not always guarantee single instance with 100% probability? Well, in some programming languages it is hard to achieve. There are also scenarios that may tolerate tiny possibility of few Singleton instances to be created. Those we identify the following singularity categories: Absolute singleton and Qualified singleton.
Absolute Singleton
Absolute Singleton guarantees 100% probability of only one instance to be created. Good example is Publisher and Subscriber pattern (example above), where publisher is required to be absolute singleton. Imagine if by accident there are two instances of publisher and some of subscribers subscribe to one of them, the other subscribers use the other instance of the publisher. Because publisher is expected to be a singleton, only one instance will be used to publish messages, leaving some subscribers without notifications.
It is harder to guarantee absolute singleton in multithreaded applications where singleton instance can be created by different threads. Code that retrieves existing singleton instance or creates new must be thread safe. In general, thread synchronization is not trivial, error prone and hard to troubleshoot. Extending more on the subject, modern multithreaded applications run on multicore CPUs that do have multiple levels of cache. A variable value cached by one CPU core may not be pushed back to RAM before thread context switches. If other thread runs on a different core, it may read stale value from RAM and those create another instance of an object. Thread synchronization code must consider this fact as well.
Example of not thread safe Singleton.
public class NotThreadSafeSingleton { private static NotThreadSafeSingleton _instance; private NotThreadSafeSingleton() { } public static NotThreadSafeSingleton Instance { get { if (_instance == null) { // Thread 1 switches here before assigning _instance. // Thread 2 runs until this point and creates 2-nd instance. _instance = new NotThreadSafeSingleton(); } return _instance; } } }
Qualified Singleton
When multiple instances can be tolerated, we can drop our guards and simplify code that creates and retrieves singleton instance. Multiple instances may be tolerated when:
- Singleton is read-only.
- Singleton instantiation has no side effects.
- Singleton instantiation does not require much of computing resources.
Great example of singleton that can be qualified, e.g. tolerate small probably of multiple instance is application configuration class MyAppConfiguration
from the example above.
Instantiation Greediness Dimension
Depends on when singleton is instantiated withing an application lifecycle we can define two categories, greedy and lazy instantiation.
Greedy Instantiation
With greedy instantiation singleton gets instantiated right on application startup or much before it is actually used. Greedy instantiation helps to avoid the need to write synchronization code around instantiation logic, those significantly simplifies implementation.
The simplest example is a singleton that is instantiated when CLR loads GreedySingleton
class first time. This may not necessarily happen on application startup, but still before GreedySingleton
instance is used first time.
public class GreedySingleton { // New instance is created when CLR loads the class. Thread safety is guaranteed. private static GreedySingleton _instance = new GreedySingleton(); private GreedySingleton() { } // The property just returns created instance. public static GreedySingleton Instance => _instance; }
Below is a little more involved example of GreedySingletonWithStartupInit
singleton that provides InitializeOnAppStartup
method to be involved by application startup code. This approach guarantees the singleton is fully instantiated before it is used.
public class GreedySingletonWithStartupInit { private static object lockObj = new object(); private static GreedySingletonWithStartupInit _instance; private GreedySingletonWithStartupInit() { } /// <summary> /// The property just returns created and initialized /// GreedySingletonWithStartupInit instance. /// If the instance is not initialized InvalidOperationException is thrown. /// </summary> public static GreedySingletonWithStartupInit Instance { get { if (_instance == null) { throw new InvalidOperationException( $"{nameof(GreedySingletonWithStartupInit)} is not initialized "); } return _instance; } } /// <summary> /// Initializes GreedySingletonWithStartupInit one time. /// Must be invoked on application startup. /// </summary> public static void InitializeOnAppStartup() { lock (lockObj) { if (_instance == null) { _instance = new GreedySingletonWithStartupInit(); _instance.Initialize(); } else { throw new InvalidOperationException( $"{nameof(GreedySingletonWithStartupInit)} already initialized"); } } } private void Initialize() { // Heavy initialization logic goes here. } }
Lazy Instantiation
Lazy singletons are instantiated at the moment when they are used first time. This approach may save some resources if we have singletons that might never be used during application life cycle, however it is harder to ensure absolute singleton with lazy instantiation in multithreaded applications.
public class LazySingleton { private static Lazy<LazySingleton> _instance = new Lazy<LazySingleton>(() => new LazySingleton()); private LazySingleton() { } // The instance is created only when it is acccessed first time. public static LazySingleton Instance => _instance.Value; }
Categorization Summary
Table below show holistic picture of Singleton classification. We can look at Singleton pattern implementations from 3 dimensions. In each dimensions we identify 2 categories.
Dimensions | Categories | |
---|---|---|
Mutability | Read Only Singleton | Mutable Singleton |
Singularity | Absolute Singleton | Qualified Singleton |
Instantiation Greediness | Greedy Instantiation | Lazy Instantiation |
Best Practices
One of the reasons why Singleton is perceived as anti-pattern is its dual nature. Singleton is useful design pattern when we need to guarantee a single instance of a class. However, at the same time it is quite easy to fall into bad development practices. Therefore, using common sense will help you implement and use Singleton safely.
About Immutability
If nothing else, then immutability makes Singleton completely safe. Data and state that do not change throughout an application life cycle may cause little to no harm; the code is easy to debug, troubleshoot and reason about.
With respect to other dimensions, with immutable singleton we can implement greedy or lazy instantiation. Immutable singleton can also be absolute or qualified, since it has no state that changes over time. Singleton that provides read-only application configuration is a good example of immutable singleton.
Mutable singletons require more careful consideration. I would recommend to implement mutable singleton as absolute to guarantee single instance and use greedy instantiation to facilitate that.
Most importantly, avoid using singleton to store global mutable variables. It is almost never a good idea and is definitely perceived as anti-pattern.
Greedy versus Lazy Instantiation
Greedy versus Lazy is quite an easy choice. With all other equal prefer greedy instantiation. Greedy instanton helps to ensure the singleton is absolute and it requires no thread synchronization code.
However, it may not be feasible to use greedy instantiation if the following is true.
- There is a rare code path where singleton is used, therefore premature instantiation is not the best option because the application is less likely to use the singleton. If you have similar scenario, I would recommend to refactor the code and avoid using singleton.
- Singleton is likely to be used, but instantiation requires significant resources (processing time and memory) and may slow down application startup.
Singleton and Dependency Injection
If your application uses Dependency Injection (DI) container, I would recommend to delegate singleton instantiation to the DI container. Majority of DI containers support variety of settings, one of which is single instance. This approach promotes consistency where control of all objects’ life cycles is delegate to DI.
There is one code readability nuance though. To determine object’s life cycle we will have to additionally consult DI configuration code where the object lifecycle is defined; looking at the class implementation code would not be enough to determine whether the object is singleton or not.
public class SingletonToBeConfiguredInDi { // There is no need to write any singleton plumbing code. } public class Program { public static void Main() { // Configure DI. var serviceProvider = new ServiceCollection() .AddSingleton<SingletonToBeConfiguredInDi>() .BuildServiceProvider(); // Use DI to retrieve the singleton instance. var singletonInstance = serviceProvider.GetService<SingletonToBeConfiguredInDi>(); var sameSingletonInstance = serviceProvider.GetService<SingletonToBeConfiguredInDi>(); } }
Summary of Best Practices
If we condense entire post in a single sentence, then it would read like this: “Singleton design pattern is useful when we need to guarantee single instance of an object, except when it is used to store mutable global state.”
The diagram below is a summary of singleton categories and how they relate to each other. The diagram aims to help you follow best practices while implementing Singleton design pattern.
As always, happy coding!
All code examples can be found on GitHub PavelHudau/BlogCodeExamples.
More design pattern article can be found here.