One Single Issue


The easiest pattern?
The Singleton design pattern is famously known among software engineers for its straightforward and easy-to-grasp implementation, even for beginners. It can be seen as the equivalent of "bubble sort" in the realm of design patterns. However, optimizing its implementation can sometimes be tricky, even for the Gang of Four, the authors of the influential book Design Patterns: Elements of Reusable Object-Oriented Software.
In this article, I'm not concerned with the criticism of the Singleton as an anti-pattern (violating the Single Responsibility Principle and introducing a global state into the application) to the extent that some call it evil. Instead, I'm here to reflect on the original implementation of this pattern, operating under the assumption that it's fully compliant with the SOLID principles.
The Original Implementation
public class Singleton {
private static Singleton INSTANCE;
private Singleton () {
}
public static Singleton getInstance() {
if(INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
The original implementation (as outlined by the GoF in their book) is very straightforward for creating a single object across the entire application: make the constructor private, and create a public entry point to access (or create) the object. That's it! Now you have one single object across the entire application.
But it turns out that it's not really a "singleton" when it comes to concurrent, multithreaded environments.
Suppose we have more than one thread accessing our entry point at the exact same time, and suppose the INSTANCE
reference is still null
(i.e., no object has been created yet). Under these assumptions, you will end up having multiple singletons in your application. (A funny oxymoron, to be honest.)
The Synchronized keyword
"Okay, that's very easy," one might say. "Just make the entry point synchronized!" This way, the block of code inside the method will be locked for a single thread at a time, ensuring that the object remains a singleton even in a multithreaded application.
public class Singleton {
private static Singleton INSTANCE;
private Singleton () {
}
public static synchronized Singleton getInstance() { //making the method synchronized
if(INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
While it’s true that this code guarantees the existence of only one object even in the presence of multiple threads, it turns out that it’s still a suboptimal way of doing it.
Note
The Cost of Synchronization
As mentioned earlier, synchronization ensures that only one thread can execute a block of code at a time, providing mutual exclusion and preventing race conditions. However, this comes with a performance penalty.
Each time a thread invokes a synchronized method, it must acquire the lock associated with that method. If another thread already holds that lock, the new thread is blocked until the lock is released. This can result in increased context switching, reduced throughput, and higher latency.
Moreover, if you think about it, after the singleton instance is initialized, synchronization becomes really unnecessary, but the overhead persists! because the getInstance()
method is still synchronized.
This is why we need a better solution, one that ensures thread safety without the cost of synchronization during every call. And the answer lies in understanding how the JVM loads and initializes classes.
First Principles
To understand the optimized implementation that we’re about to see, we need to first understand how the JVM (Java Virtual Machine) handles class loading.
In Java, class loading and initialization follow a strict and well-defined process. When the JVM loads a class for the first time, it goes through several phases: loading, linking, and initialization. A crucial fact is that class initialization is guaranteed by the JVM to be thread-safe. This means that:
Static initializers (
static
fields and blocks) are executed exactly once, andOnly one thread executes the initialization code, while other threads block until it is done.
Furthermore, inner static classes are not loaded until they are explicitly referenced. This is key to implementing an efficient and thread-safe singleton: by leveraging Java's class loading mechanism, we can ensure both lazy initialization and thread safety without any explicit synchronization.
Again, the previous implementation (when we used a synchronized entry point) perfectly does the job (lazy initialization & thread safety), but it doesn’t do so very optimally because of the cost of synchronization.
Let the JVM handle it
public class Singleton {
private Singleton() {}
private static class SingletonHolder { //introducing an inner static class
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
The JVM ensures that class initialization (specifically, initializing INSTANCE
) is atomic and happens only once, even when multiple threads try to access it concurrently. In addition, unlike the synchronized
version, this approach has zero performance penalty in the common case (i.e., after the instance is initialized), so need for any locking mechanism that will cause an unnecessary overhead.
Subscribe to my newsletter
Read articles from Osama Ismail directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
