Singleton patterns are a common design pattern used in software development to ensure a class has only one instance and provides a global point of access to it. While there are several ways to implement a Singleton in Java, one of the most efficient and recommended methods is the Initialization-on-Demand Holder Idiom, also known as the Bill Pugh Singleton. This method leverages the Java language’s guarantees about class initialization, ensuring thread safety and lazy loading without requiring explicit synchronization.
In this blog, we’ll delve into the Bill Pugh Singleton pattern, understand why it’s effective, and implement it in Kotlin.
Bill Pugh is a computer scientist and professor emeritus at the University of Maryland, College Park. He is well-known for his contributions to the field of computer science, particularly in the areas of programming languages, software engineering, and the Java programming language.
One of his most notable contributions is the development of the Skip List, a data structure that allows for efficient search, insertion, and deletion operations. However, in the Java community, he is perhaps best known for his work on improving the thread safety and performance of Singleton pattern implementations, which led to the popularization of the Initialization-on-Demand Holder Idiom, commonly referred to as the Bill Pugh Singleton pattern.
Revisiting the Singleton
The Singleton pattern restricts the instantiation of a class to one “single” instance. This pattern is useful when exactly one object is needed to coordinate actions across the system.
Basic Singleton Implementation in Kotlin
object BasicSingleton {
fun showMessage() {
println("Hello, I am a Singleton!")
}
}
Here, Kotlin provides a concise way to define a Singleton using the object
keyword. However, the object
declaration is eagerly initialized. If your Singleton has costly initialization and might not always be needed, this could lead to inefficient resource usage.
The Problem with Early Initialization
In some cases, you might want the Singleton instance to be created only when it is needed (lazy initialization). Traditional methods like synchronized
blocks can ensure thread safety but can lead to performance bottlenecks. While this approach is more efficient, it involves synchronization during every access, which can be a performance bottleneck. This is where the Bill Pugh Singleton comes into play.
The Initialization-on-Demand Holder Idiom
The Bill Pugh Singleton pattern, or the Initialization-on-Demand Holder Idiom, ensures that the Singleton instance is created only when it is requested for the first time, leveraging the classloader mechanism to ensure thread safety.
Key Characteristics:
- Lazy Initialization: The Singleton instance is not created until the
getInstance()
method is called. - Thread Safety: The class initialization phase is thread-safe, ensuring that only one thread can execute the initialization logic.
- Efficient Performance: No synchronized blocks are used, which avoids the potential performance hit.
Bill Pugh Singleton Implementation in Kotlin
Let’s implement the Bill Pugh Singleton pattern in Kotlin.
Step-by-Step Implementation
- Define the Singleton Class:We first define the Singleton class but do not instantiate it directly. Instead, we define an inner static class that holds the Singleton instance.
- Inner Static Class:The static inner class is not loaded into memory until the
getInstance()
method is called, ensuring lazy initialization. - Accessing the Singleton Instance:The Singleton instance is accessed through a method that returns the instance held by the inner static class.
class BillPughSingleton private constructor() {
companion object {
// Static inner class - inner classes are not loaded until they are referenced.
private class SingletonHolder {
companion object {
val INSTANCE = BillPughSingleton()
}
}
// Method to get the singleton instance
fun getInstance(): BillPughSingleton {
return SingletonHolder.INSTANCE
}
}
// Any methods or properties for your Singleton can be defined here.
fun showMessage() {
println("Hello, I am a Bill Pugh Singleton in Kotlin!")
}
}
fun main() {
// Get the Singleton instance
val singletonInstance = BillPughSingleton.getInstance()
// Call a method on the Singleton instance
singletonInstance.showMessage()
}
Outpute:
Hello, I am a Bill Pugh Singleton in Kotlin!
Here is the explanation of the Implementation,
- Private Constructor: The
private constructor()
prevents direct instantiation of the Singleton class. - Companion Object: In Kotlin, the
companion object
is used to hold the Singleton instance. The actual instance is inside theSingletonHolder
companion object, ensuring it is not created until needed. - Lazy Initialization: The
SingletonHolder.INSTANCE
is only initialized whengetInstance()
is called for the first time, ensuring the Singleton is created lazily. - Thread Safety: The Kotlin classloader handles the initialization of the
SingletonHolder
class, ensuring that only one instance of the Singleton is created even if multiple threads try to access it simultaneously. In short, The JVM guarantees that static inner classes are initialized only once, ensuring thread safety without explicit synchronization.
Many of us don’t believe in thread safety. Let’s see a practical demonstration of the Bill Pugh Singleton’s thread safety.
Practical Demonstration of Thread Safety
Let’s demonstrate this with a Kotlin example that spawns multiple threads to try to access the Singleton instance concurrently. We will also add logging to see when the instance is created.
class BillPughSingleton private constructor() {
companion object {
private class SingletonHolder {
companion object {
val INSTANCE = BillPughSingleton().also {
println("Singleton instance created.")
}
}
}
fun getInstance(): BillPughSingleton {
return SingletonHolder.INSTANCE
}
}
fun showMessage(threadNumber: Int) {
println("Hello from Singleton instance! Accessed by thread $threadNumber.")
}
}
fun main() {
val numberOfThreads = 10
val threads = Array(numberOfThreads) { threadNumber ->
Thread {
val instance = BillPughSingleton.getInstance()
instance.showMessage(threadNumber)
}
}
// Start all threads
threads.forEach { it.start() }
// Wait for all threads to finish
threads.forEach { it.join() }
}
Singleton Creation Logging: The also
block in val INSTANCE = BillPughSingleton().also { ... }
prints a message when the Singleton instance is created. This allows us to observe exactly when the Singleton is initialized.
Multiple Threads: We create and start 10 threads that each tries to get the Singleton instance and call showMessage(threadNumber)
on it.
Thread Join: join()
ensures that the main thread waits for all threads to finish execution before proceeding.
Expected Output
If the Bill Pugh Singleton pattern is indeed thread-safe, we should see the “Singleton instance created.” message exactly once, no matter how many threads attempt to access the Singleton simultaneously.
Singleton instance created.
Hello from Singleton instance! Accessed by thread 0.
Hello from Singleton instance! Accessed by thread 1.
Hello from Singleton instance! Accessed by thread 2.
Hello from Singleton instance! Accessed by thread 3.
Hello from Singleton instance! Accessed by thread 4.
Hello from Singleton instance! Accessed by thread 5.
Hello from Singleton instance! Accessed by thread 6.
Hello from Singleton instance! Accessed by thread 7.
Hello from Singleton instance! Accessed by thread 8.
Hello from Singleton instance! Accessed by thread 9.
Note: Ideally, this sequence is not seen. However, for simplicity, I have shown it in this order. Otherwise, it would be in a random order.
Hence, the output demonstrates that despite multiple threads trying to access the Singleton simultaneously, the instance is created only once. This confirms that the Bill Pugh Singleton pattern is indeed thread-safe. The JVM handles the synchronization for us, ensuring that even in a multithreaded environment, the Singleton instance is created safely and efficiently.
Advantages of Using Bill Pugh Singleton
- Thread-Safe: The pattern is inherently thread-safe, avoiding the need for synchronization.
- Lazy Initialization: Ensures that the Singleton instance is created only when needed.
- Simple Implementation: It avoids the boilerplate code associated with other Singleton implementations.
- Readability: The code is concise and easy to understand.
Conclusion
The Bill Pugh Singleton, or Initialization-on-Demand Holder Idiom, is an elegant and efficient way to implement the Singleton pattern, especially when you need lazy initialization combined with thread safety. Kotlin’s powerful language features allow for a concise and effective implementation of this pattern.
This pattern is ideal when working on large applications where resources should be allocated efficiently, and thread safety is a concern. By understanding and utilizing this pattern, you can enhance the performance and reliability of your Kotlin applications.