This continues my series on ways you’ve probably used design patterns in real-life and may not have even known it. The previous post was on the Iterator Design Pattern.
The original book on software design patterns, Design Patterns: Elements of Reusable Object-Oriented Software discussed three types of design patterns, creational, structural and behavioral. As time has continued we’ve found a need for further patterns to cover other targeted areas. One of those areas is concurrency patterns that specifically addresses patterns for multi-threaded software development.
With the ease of the TPL and async/await integrated into .NET, multi-threaded software development should be the standard. Microsoft has gone to great pains to make multi-threaded development ridiculously easy. In fact it actually causes some hesitancy when I consider doing development in any other languages. I had been hoping when Swift was released that Apple had the forethought to improve the horrendous multi-threading model in Objective-C but they did not. In defence of Objective-C however, it’s not any worse than most other languages.
This brings us to the double-checked locking design pattern that is a part of the concurrency patterns. Consider the following code:
public class Singleton
{
private Singleton() { /* Some long task to initialize */ }
private static Singleton _instance = null;
public static Singleton Instance
{
get
{
if (_instance == null)
_instance = new Singleton();
return _instance;
}
}
}
The problem with this code is that you run the potential for multiple instances of Singleton to be running around when you only wanted one. Here is the scenario:
Thread 1, line 10: Since this is the first access _instance is null.
CONTEXT SWITCH
Thread 2, line 10: Hmm, _instance is null.
CONTEXT SWITCH
Thread 1, line 11: Well, since _instance is null we have to create a new instance.
Thread 1, line 12: return the _instance we just created.
CONTEXT SWITCH
Thread 2, line 11: Well, since _instance is null we have to create a new instance.
Thread 2, line 12: return the _instance we just created.
Here is a perfect scenario to utilize locking. Locking is a pattern that allows a thread exclusive access to a segment of code without having to worry about other threads entering the block of code.
public class Singleton
{
private Singleton() { /* Some long task to initialize */ }
private static object lockObj = new object();
private static Singleton _instance = null;
public static Singleton Instance
{
get
{
lock (lockObj)
{
if (_instance == null)
_instance = new Singleton();
}
return _instance;
}
}
}
We can see that the lock will block the constructor until the current thread is done. Here is the scenario:
Thread 1, line 11: No ones here so let’s go into the lock.
Thread 1, line 13: Since this is the first access _instance is null.
CONTEXT SWITCH
Thread 2, line 11: Hmm, some other thread is here already, I’ll have to wait.
CONTEXT SWITCH
Thread 1, line 14: Well, since _instance is null we have to create a new instance.
Thread 1, line 15: Done with the lock.
CONTEXT SWITCH
Thread 2, line 11: Whoever was here is done, let’s get going.
Thread 2, line 13: _instance has a value.
CONTEXT SWITCH
Thread 1, line 16: return _instance.
CONTEXT SWITCH
Thread 2, line 16: return _instance.
Now there are some problems with locking, most notably dead locking. This occurs when there is a lock on thread 1 but thread 2 is waiting for thread 1 to finish. Neither can continue and your application locks up. Additionally, by utilizing locks you turn a small piece of code into a bottleneck and eliminate a lot of the benefits of multi-threading. If threads can only enter the lock one at a time to see if _instance is null, what are we really gaining here?
This is where the double-checked locking pattern comes in handy. Why go into the lock if you don’t need to?
public class Singleton
{
private Singleton() { /* Some long task to initialize */ }
private static object lockObj = new object();
private static Singleton _instance = null;
public static Singleton Instance
{
get
{
if (_instance == null) //first check
{
lock (lockObj)
{
if (_instance == null) //second check
_instance = new Singleton();
}
}
return _instance;
}
}
}
We just check to see if the value is null and if it isn’t we don’t even have to consider locking. Here is the scenario:
Thread 1, line 11: Since this is the first access _instance is null.
Thread 1, line 13: No ones here so let’s go into the lock.
CONTEXT SWITCH
Thread 2, line 11: Hmm, _instance is null.
Thread 2, line 13: Hmm, some other thread is here already, I’ll have to wait.
CONTEXT SWITCH
Thread 1, line 16: Well, since _instance is null we have to create a new instance.
Thread 1, line 17: Done with the lock.
CONTEXT SWITCH
Thread 3, line 11: _instance has a value.
Thread 3, line 19: return _instance.
CONTEXT SWITCH
Thread 2, line 11: Whoever was here is done, let’s get going.
Thread 2, line 15: _instance has a value.
CONTEXT SWITCH
Thread 1, line 19: return _instance.
CONTEXT SWITCH
Thread 2, line 19: return _instance.
This scenario presents the best of both worlds (which is why it’s a pattern), it allows you to safely construct your singleton while still minimizing locking. But as I’m sure you know, in .NET we can do better. As I mentioned earlier Microsoft has gone to some pretty great pains to make multi-threading easier for us. One of those ways is by using Lazy.
public class Singleton
{
private Singleton() { /* Some long task to initialize */ }
private static readonly Lazy<Singleton> _lazyInstance = new Lazy<Singleton>(() => new Singleton());
public static Singleton Instance
{
get { return _lazyInstance.Value; }
}
}
All this is doing is taking out the need for double-checked locking by doing it for you. That’s it. Now, this is the most basic use of Lazy. If you read the reference you have a lot of control in terms of what happens when exceptions are thrown in the constructor as well as other aspects so if you take fully take advantage of it you’ll see there is a lot more it can do.
And all that was done to get you here. Lazy initialization is part of the creational patterns from GoF. But it’s implementation is done using the Proxy design pattern. Lazy uses the proxy design pattern for controlling access to the value of the instance. At it’s most generalized definition, the proxy design pattern “is a class functioning as an interface to something else.” It’s definition is incredibly broad. In addition to Lazy, Facade, Flyweight and Adapter patterns could all also be called types of proxy patterns, though that is only in the loosest sense of the definition. Generally proxy is considered applicable where there is a heavy expense or long load time to initializing or constructing an object.
Imagine if you are working with images. Most of the time you are looking at the meta-data about the image, size, date created, etc… This is so you can sort them, organize them, move them around. Sure, occasionally you want to open the image but this is an expensive task that could eat a lot of memory. To handle this scenario you create a “meta-image” object (or proxy). This provides for all the information about the image without having to load it into memory. Then, when the code needs the image, the “meta-image” object could provide for the ability to retrieve the real image and thus you have your proxy design pattern.
Thanks for reading,
Brian