Singleton Design Pattern
This a very common and simple design pattern
The Singleton design pattern is a creational pattern that ensures the creation of only one instance of a class and provides a global point of access to it. Here’s an example of implementing the Singleton pattern in Kotlin:
object Singleton {
init {
// Initialization code (optional)
}
fun doSomething() {
// Perform operations
}
}In this example, we define a singleton class called Singleton using the object keyword. The object keyword creates a singleton instance of the class.
The init block is used for optional initialization code that runs when the instance is created. You can put any necessary initialization logic inside this block.
The doSomething() function is an example of a method that can be called on the singleton instance. You can add any other methods or properties to the singleton class as needed.
To use the Singleton, you can simply call the methods or access the properties on the Singleton instance:
Singleton.doSomething()Since the Singleton pattern ensures that only one instance of the class exists, multiple calls to Singleton.doSomething() will always refer to the same instance.
Get Amit Dhiman’s stories in your inbox
Join Medium for free to get updates from this writer.
It’s important to note that the Kotlin language provides built-in support for creating singletons using the object keyword, which simplifies the implementation of the Singleton pattern.
How you will make singleton class thread-safe
Ensuring thread safety is crucial when implementing a singleton class to prevent issues that may arise from concurrent access by multiple threads. Here are a few approaches to achieving thread safety in a singleton class implementation:
Eager Initialization:
- Eagerly initialize the singleton instance when the class is loaded. This guarantees thread safety because the instance is created in a thread-safe manner by the JVM.
- This approach works well if the initialization process is not resource-intensive and the instance creation is not dependent on any external factors.
Example:
object Singleton {
// Initialization code
}Synchronized Accessor:
- Use synchronized access to the getInstance() method to ensure that only one thread can access it at a time.
- This approach introduces a lock overhead, potentially affecting performance.
Example:
object Singleton {
@Volatile
private var instance: Singleton? = null
@Synchronized
fun getInstance(): Singleton {
if (instance == null) {
instance = Singleton()
}
return instance!!
}
}Double-Checked Locking:
- Use double-checked locking to reduce the overhead of synchronization.
- This approach performs an initial non-synchronized check and then uses synchronized access only when creating the instance.
Example:
object Singleton {
@Volatile
private var instance: Singleton? = null
fun getInstance(): Singleton {
if (instance == null) {
synchronized(this) {
if (instance == null) {
instance = Singleton()
}
}
}
return instance!!
}
}Thread-Safe Initialization-on-Demand Holder (Bill Pugh Singleton):
- Utilize the Initialization-on-Demand Holder (IODH) idiom, which leverages the JVM’s class-loading guarantees for thread safety.
- The singleton instance is created inside a nested class, ensuring lazy initialization and thread safety.
Example:
class Singleton private constructor() {
companion object {
val instance = SingletonHolder.INSTANCE
}
private object SingletonHolder {
val INSTANCE = Singleton()
}
}It’s important to note that starting from Kotlin 1.12, the synchronized keyword is no longer necessary for lazy initialization as Kotlin's lazy delegate handles synchronization internally, ensuring thread safety in a more concise and efficient manner.
Remember to choose the thread safety mechanism that suits your specific requirements and consider the trade-offs in terms of performance and code complexity.



