Deconstructing The Singleton

As far as software design patterns go, the singleton has long been considered one of the least complex. But there are pitfalls to its implementation that require nuance. While this article does not attempt to come up with anything revolutionary (the implementation details described here are well documented), it does try to showcase the thought process involved when developing the solution. New developers and junior engineers may find this process valuable because as we’ll see, most academic solutions fall short in real-world scenarios.

Conceptually, a singleton is not hard to understand. Wikipedia defines the singleton pattern as follows:

In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system. The concept is sometimes generalized to systems that operate more efficiently when only one object exists, or that restrict the instantiation to a certain number of objects. The term comes from the mathematical concept of a singleton.

A database connection, an access token, a Domain-driven design repository. These are all examples of potential singletons. The application only needs to instantiate one object (instance) of each class. The instance is then reused as needed for the lifetime of the running app. The application does not instantiate a new object for every use. Simple enough. So let’s take a look at a basic implementation.

public class MySingleton {
    private static MySingleton INSTANCE;
    public static MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}


return INSTANCE;
}
    ...
}

Here we have the MySingleton class which has a private static member called INSTANCE, and a public static method called getInstance(). The first time getInstance() is called, the INSTANCE member is null. The flow will then fall into the creation condition and create a new instance of the MySingleton class. Subsequent calls to getInstance() will find that the INSTANCE variable is already set, and therefore not create another MySingleton instance. This ensures there is only one instance of MySingleton which is shared among all callers of getInstance().

NOTE: In this implementation, the constructor for MySingleton (not shown) is private and as such, can not be called from outside the class. This forces construction through the public static method getInstance().

This implementation has a problem. Multi-threaded applications will have a race condition on the creation of the single instance. If multiple threads of execution hit the getInstance() method at (or around) the same time, they will each see the INSTANCE member as null. This will result in each thread creating a new MySingleton instance and subsequently setting the INSTANCE member. What if the creation of the MySingleton instance takes time? Or uses a finite resource? The consequences of this not being thread-safe could lead to problems. Let’s fix that.

private static MySingleton INSTANCE;
public static synchronized MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}

return INSTANCE;
}

Here we’ve used the synchronized keyword in the method signature to synchronize the getInstance() method. This will certainly fix our race condition. Threads will now block and enter the method one at a time. But it also creates a performance problem. Not only does this implementation synchronize the creation of the single instance, it synchronizes all calls to getInstance(), including reads. Reads do not need to be synchronized as they simply return the value of INSTANCE. Since reads will make up the bulk of our calls (remember, instantiation only happens on the first call), we will incur an unnecessary performance hit by synchronizing the entire method.

NOTE: There are cases where a singleton can be re-inistantiated AFTER the initial call. This might occur when the singleton’s instance has an expiration. In this case, a check on expiration would happen in conjunction with the check on existence. Remember this use-case. We’ll come back to it at the end of the article.

We can fix our performance bottleneck by localizing our thread synchronization to the operation that requires it. To do this we can use a synchronized block around the creation of the instance instead of synchronizing the entire method.

private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronize(MySingleton.class) {
INSTANCE = new MySingleton();
}
}

return INSTANCE;
}

Here we’ve moved synchronization from the method signature, to a synchronized block that wraps the creation of the MySingleton instance. But does this solve our problem? Well, we are no longer blocking on reads, but we’ve also taken a step backward. Multiple threads will hit the getInstance() method at or around the same time and they will all see the INSTANCE member as null. They will then hit the synchronized block where one will obtain the lock and create the instance. When that thread exits the block, the other threads will contend for the lock, and one by one each thread will fall through the block and create a new instance of our class. So we are right back where we started.

Let’s think through a solution.

The problem is that once we enter the synchronized block, we assume the INSTANCE member still needs to be initialized. As we’ve determined, this is an invalid assumption. So let’s issue another check from INSIDE the block. If the INSTANCE member has already been set, we’ll skip initialization. This is called double-checked locking.

private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
    return INSTANCE;
}

Here we first check if the INSTANCE member has been initialized without obtaining the lock. If not, we obtain the lock, and then double-check whether the member has been initialized. If the member has not been initialized by a previous thread, we initialize it.

This solves our problem of multiple instantiation. But once again, our solution has presented another challenge. Other threads might not “see” that the INSTANCE member has been updated. This is because of how Java optimizes memory operations. Threads copy the original values of variables from main memory into the CPU’s cache. Changes to values are then written to, and read from, that cache. This is a feature of Java designed to optimize performance. But this creates a problem for our singleton implementation. A second thread — being processed by a different CPU or core, using a different cache — will not see the changes made by the first. This will cause the second thread to see the INSTANCE member as null forcing a new instance of our singleton to be created.

So now what?

Well, we can solve this by using the volatile keyword on the declaration of the INSTANCE member. This will tell the compiler to always read from, and write to, main memory, and not the CPU cache.

private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
    return INSTANCE;
}

But this simple change comes at a cost. Because we are bypassing the CPU cache, we will take a performance hit each time we operate on the volatile INSTANCE member — which we do 4 times. We double-check existence (1 and 2), set the value (3), and then return the value (4). One could argue that this path is the fringe case as we only create the instance during the first call of the method. Perhaps a performance hit on creation is tolerable. But even our main use-case, reads, will operate on the volatile member twice. Once to check existence, and again to return its value. We seem to be on the right track. But we’ll need to figure out how to optimize this for better performance.

Since the performance hit is due to operating directly on the volatile member, let’s set a local variable to the value of the volatile and operate on the local variable instead. This will decrease the number of times we operate on the volatile, thereby reclaiming some of our lost performance.

private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
MySingleton result = INSTANCE;
if (result == null) {
synchronized(MySingleton.class) {
result = INSTANCE;
if (result == null) {
INSTANCE = result = createInstance();
}
}
}
    return result;
}

Note that we have to set our local variable again when we enter the synchronized block. This ensures it is up to date with any changes that occured while we were waiting for the lock.

This is really good and it certainly seems to be the best we can do. We were able to address each problem that came up, and we were able to identify that the problems existed because of a deep of understanding of the Java language. The result is a well crafted implementation of the singleton pattern.

But let’s not declare success just yet.

We got here by continuing down a path that was defined by our first implementation. Remember that not-so thread-safe static factory method? Each time a problem came up, we addressed it in place without ever rethinking our original approach. Has the final solution been too influenced by that original design? Have we been myopic and narrow in focus? Have we simply maintained the azimuth on which we started?

There comes a time when every good engineer says to themselves, “Stop. I’m trying too hard. There has to be a better way.” This. Above all else. Is your badge as a software craftsman.

So now that we’ve arrived at a solution it might be time to take a step back. Is there a way to make this simpler? As we’ll see in a bit, that answer depends on our use-case. But for now, let’s indulge our curiosity. What were the problems we solved? Thread safety. Variable visibility. Where are these things inherent and managed for us?

Class loading.

private static final MySingleton INSTANCE = new MySingleton();
public static MySingleton getInstance() {
return INSTANCE;
}

Well this is certainly less complex! Here our instance is instantiated when we load the class. We don’t need to concern ourselves with locks or variable visibility. This is a legit solution for our example use-case. But if we’ve learned anything today, it is that everything has tradeoffs.

So what’s the catch?

In our previous implementation we did not initialize the INSTANCE member until the first time we called getInstance(). That is called lazy initialization, or on-demand initialization. In this case, we initialize the member when the class is loaded (up front) regardless of whether the instance is ever read via the getInstance() method. This is called eager initialization. It might not be a concern. But again, depending on the situation, it could be.

How might we solve for this?

Knowing that private inner classes are not loaded when the outer class is loaded, let’s move our singleton’s instantiation into an inner “holder” class, and load it on-demand from within our static factory method.

public class MySingleton {
private static class Holder {
public static final MySingleton INSTANCE
= new MySingleton();
}
    public static MySingleton getInstance() {
return HOLDER.INSTANCE;
}

Voila! We’ve now have the best of both worlds. We can use class loading to bypass the need to manage thread synchronization and variable visibility, while at the same time providing lazy initialization of the MySingleton instance (if that is what we want). This might be the best implementation thus far for our simple no-arg constructor singleton example.

But does this approach work if we ever need to re-create the instance? Under what circumstances might we want to do that? Does this approach only work with a no-arg constructor? While this might be a textbook example, an academic example, of implementing a singleton, you may find the previous double-checked volatile implementation more apt for real-word scenarios.

Let’s take a look at a more complex (real-world) use-case.

public class BearerTokenFactory {
private String clientId;
private String secret;
private String scope;
private WebTarget webTarget;
private final MacSigner macSigner;
private volatile BearerToken instance;
    public BearerTokenFactory(
WebTarget webTarget,
String clientId,
String secret,
String realm,
String scope) {
        super();
        setWebTarget(webTarget); 
setClientId(clientId);
setSecret(secret);
setRealm(realm);
setScope(scope);

this.macSigner
= new MacSigner(
this.secret);
}
    public BearerToken get() {
BearerToken result = instance;
if (result == null || result.hasExpired()) {
synchronized(this) {
result = instance;
if (result == null || result.hasExpired()) {
result = instance = newBearerToken();
}
}
}
        return result;
}
    private BearerToken newBearerToken() {
make request to authorization server
handle 400s and 500s
deserialize a 200 into a BearerToken object
handle deserialization exceptions
return the BearerToken.

}
    ...setters and other private methods not shown...
}

Here we have a BearerTokenFactory that is configured with our clientId and clientSecret. Through this factory we can get a BearerToken (Access Token) from an authorization server. Simple enough. But what we do not want to do is get a new BearerToken each time we call get on our factory. Tokens are good for a specified period of time. So we want to keep our token and reuse it until it has expired. The first time we call the factory’s get method, the (now lower-case)instance member is null. The factory will therefore call the authorization server, handle unexpected responses, and create a BearerToken object. That object is then reused as a singleton until it expires. Once expired, a new token will be fetched from the authorization server and set as our singleton. The simple “holder” class-loading solution would not work here. So we had to revisit our original double-checked volatile solution, move the implementation to a factory, and add a compound condition which also checks for token expiration.

So that’s it. As we’ve seen, the singleton is not as simple as most people think. There are clearly situations where we must manage thread safety and variable visibility ourselves and not rely on the “holder” solution. Because the journey to our final solution exposed a new challenge at every turn, the “simple” singleton pattern is one of my favorite examples of implementation nuance, craft, and iterative thought.

Did you find this article useful? Add some claps by clicking the hands at the bottom of the page.

ABOUT THE AUTHOR:
Michael Andrews is a hands-on engineering lead and platform architect with 20 years of experience building scalable high-performance applications, APIs, and backend systems and services. Experienced thought leader with success leading engineering teams, and designing software and architectures which support evolving business needs. Committed to developing light-weight malleable software and decoupled event-driven code. Experienced cloud architect. Specialist in DevOps, CICD, and blue/green fully tested low-risk no down-time automated deployments.

TECHNICAL PROFICIENCIES:
Backend architecture and platform engineering; Java and JVM performance tuning; distributed message-based systems; multi-threaded programming and concurrency; RESTful web services secured with OAuth2; Domain-driven Design and Hexagonal Architecture (ports and adaptors); various persistence technologies such as JPA, Hibernate, MySQL, and MongoDB; The Spring Framework including Spring Data, Spring AOP, Spring Security, and Spring AMQP; JavaEE and Tomcat; various client-side technologies and frameworks; Chef; Jenkins; GIT; Maven; Artifactory; Docker; Kubernetes; Helm; DevOps and CICD including automating infrastructure, testing, deployments, and business continuity and disaster recovery; Amazon Web Services (AWS), with extensive use of VPC, CloudFormation, Route53, ECR, Kinesis, Athena, RDS, S3, and IAM.