Singled Out
One of my colleagues blindsided me the other day with a question about software architecture that I should’ve been able to answer on the spot: “Are singletons really all that bad?”
The Singleton pattern is one of the common solutions identified in Design Patterns. At its core, a Singleton is a class that only allows one instance. All clients of the class will operate against the same instance. The implementation outlined in Design Patterns provides a class-level function replacing the class’ constructor in its public API which manages the existence of the singleton instance.
A fairly stock Java Singleton implementation for, for example, sending mail runs like this:
public class Mailer { private static final Mailer instance = new Mailer (); public static Mailer getInstance () { return instance; } /* Note the access specifier: no other class can access this constructor. */ private Mailer () { /* ... */ } public void sendMail (String recipient, String message) { /* ... */ } }
From an implementor’s point of view, Singleton classes cost almost nothing, either in terms of extra code to maintain or in terms of assumptions to test. In the average application, each class providing some service to the application only has one instance, so constraining the class to only one instance isn’t a huge leap. These kinds of classes tend to be stateless, or almost stateless, so clients don’t necessarily need to be aware that they’re sharing instances with other clients as there are no extra synchronization or garbage collection hazards to handle. There are even times when Singletons are the right thing: either they accurately model some aspect of the problem where only a single representative can exist (for example, Java’s Runtime class, which exposes services provided by the JVM running the program), or the resulting API is compellingly simple and easy to use (for example, Log4J’s Logger, which is effectively backed by a Singleton registry of Logger instances).
Implementing a Singleton correctly can easily depend on the dark corners of the language’s object model. The most common source of Singleton implementation bugs are caused by language or deployment models that scope “global” state to only part of the system. Networked systems can easily have an instance on every node, and applications that have multiple AppDomains or ClassLoaders can easily create multiple Singleton instances in a single address space. In multithreaded apps (that is, almost anything written this decade), Singletons can also be threading hazards: if they manage access to an external resource, then it’s very easy to build a synchronization bottleneck into your entire application in the name of keeping access to that resource thread-safe.
Singletons cause most of the problems associated with them on the consumer side. The most common implementation pattern for Singleton clients is very simple: every occurrence of
Mailer m = new Mailer (/* ... */);
gets replaced with
Mailer m = Mailer.getInstance ();
This wins nothing: the client is tightly bound to a number of assumptions about the identity and lifecycle of the provider. In fact, it loses a little: instead of giving each client its own isolated state, now there’s a guarantee that some state is shared between client instances, via the Singleton instance. There are also knock-on effects: because the client controls exactly which provider it uses, it’s difficult to isolate only the client class for testing. It also becomes difficult, if not impossible, to insert behaviour between the client and the singleton provider – no AOP, no proxies, and no remoting, without making invasive changes to the client or the singleton.
Over the long term, as software evolves, patterns tend to propagate. Inline getInstance() calls for Singleton clients tend to get repeated in new code. In a sufficiently evolved codebase that uses Singletons extensively, a badly-placed getInstance() call can easily cause half the objects in the system to spring into being and initialize themselves. Even when this doesn’t cause performance issues on some code paths, it can easily cause startup and initialization order to become unpredictable during apparently-safe changes. Remembering which code paths are “supposed” to initialize singletons becomes an extra form of friction for developers working on the codebase.
Singletons aren’t popular by accident, though. In most modern object-oriented systems, there is a constellation of core classes which, in the final system, only need to exist once. The Mailer example I’ve been using is actually a good example: if it manages connections to the underlying mail system itself in a sane way, then there probably only needs to be a single instance of Mailer in the deployed app. If Mailer handles any connection pooling, then ensuring the creation of new instances is carefully controlled becomes almost mandatory. Singletons provide a really simple way to provide a single instance to an application.
Most development time, thought, and effort goes into the code paths that get used in the final product, so there’s an easy conceptual leap to make from “the system I’m building only needs one instance” to “this object should only allow one instance.” Developers without fairly extensive experience with mocking and other unit-testing techniques, or who don’t value the ability to insert new behaviour without changing existing code, can easily see no problem with using Singletons to manage lifetime and to provide access to the instance, since it’s convenient.
There are alternatives to Singletons for managing service objects: the standard Java examples are EJBs, which delegates control over object lifecycles to the app container, and Spring, which takes responsibility for lifecycle management and wiring between providers and consumers within the app. Other languages have similar tools available, either in the language or as a library. However, these tools have a higher barrier to entry than “just another Singleton”: even the simplest inversion of control implementation is more lines of code than a Singleton.
The kinds of problems Singletons cause only really become problems in medium-sized and larger codebases. Not coincidentally, medium-sized and larger codebases tend to be the product of long evolution from a small, relatively clean initial product, which means there’s been lots of time for people to forget the program’s overall structure. Refactoring to clean up the problems isn’t difficult, but surprisingly little has been written about it.
For most Singletons, where obtaining an instance is a parameter-less call to a getInstance()-like method, refactoring that call out to an injected dependency via a constructor or method parameter is a low-risk change that can be made and tested for one class at a time. Most, if not all, getInstance() calls follow a very simple, predictable code path, which means there are no side effects to take care of, and no changes at all have to happen in the provider. The only potential “gotcha” is the first call, which may do initialization work. This kind of change also has high rewards: injected dependencies can be replaced with alternate implementations, which makes testing much easier and lets you augment or replace the implementation without altering the clients.
There’s more risk in changing the Singleton itself first. Refactoring the lifecycle management code out of the Singleton and into a higher layer does help, but while clients are calling getInstance() directly, any change to the lifecycle management means changing every client at the same time, which in turn means you need to re-test all of those clients. Once the clients have been refactored, though, it’s much safer and easier to change the lifecycle managment.
Singletons are not the Great Evil they’re sometimes depicted as. However, they’re used in a lot of situations as a crutch to avoid writing real inter-object dependency management, to the detriment of software using them. Their convenience can outweigh the problems, especially in small systems or prototypes, but you need to have a way out when they start to cause problems.
Next time: implementation examples, for Spring.
Edited at March 11, 2009 at 12:45 am: I weeded the links a bit – it sounded too much like Jeff Atwood. If you want to know what I removed, drop a comment.Edited again at March 12, 2009 at 2:23 pm: HTML fail. Thanks, Justin!
2 Comments
Other Links to this Post
-
The Grimoire » Singling Out to Spring — March 11, 2009 @ 10:44 am
RSS feed for comments on this post. TrackBack URI

By larry, March 11, 2009 @ 1:18 pm
yup :)