How to Create a Singleton in Kotlin
- Create a Singleton Using the Kotlin API
- Create a Singleton Using a Companion Object
- Create a Thread-Safe Singleton Using a Companion Object
- Create a Singleton Using the Lazy Initializer
- Conclusion
Design patterns are a set of solutions to common problems encountered based on the requirement at hand, and these solutions can be reused in other systems trying to solve the same problem. There are different design patterns, including creational, behavioral, and structural design patterns.
Creational design pattern deals with how objects are created, and in this tutorial will learn how to use the singleton pattern, which is an example of the creational design pattern.
The singleton pattern is a design pattern that helps to create a single copy of an object and provide one point of access to it.
When developing applications, there are situations whereby we are required to have a single copy of an object, such as objects for thread pools, logging, device drivers, registry settings, caches, and others.
A program with multiple copies of these objects can lead to incorrect behavior, resource overuse, and inconsistent results. In this tutorial, we will learn different ways we can use to create a singleton in Kotlin.
Create a Singleton Using the Kotlin API
Kotlin provides a singleton that we can use out of the box without writing any object creation code for it. To achieve this, we must define our singleton name using the object
keyword, and inside this singleton, we can add any member variables and functions.
Open IntelliJ and select File > New > Project
. On the window that opens, enter the project name as kotlin-singleton
, select Kotlin on the Language section, and Intellij on the Build system section.
Finally, press the Create
button to generate the project.
Create a file named Main.kt
under the src/main/kotlin
folder and copy and paste the following code into the file.
object Singleton{
fun showMessage(): Singleton {
return Singleton
}
}
fun main(){
println(Singleton.showMessage())
println(Singleton.showMessage())
}
As mentioned above, this is the default way of creating a singleton in Kotlin and is very easy to implement. Note that the singleton object created is thread safe which means that even multithreaded programs cannot create a new object which might lead to data inconsistency.
In this code, we have created a singleton with the name Singleton
and a member function named showMessage()
. The function returns the reference to the singleton that is created.
Run this code and note that the two calls to the member function return a reference to the same object, as shown below.
Singleton@5305068a
Singleton@5305068a
Create a Singleton Using a Companion Object
Comment out the previous example and copy and paste the following code into the Main.kt
file after the comment.
class Singleton private constructor(){
companion object{
var singleton = Singleton();
fun getInstance(): Singleton{
if (singleton == null){
singleton = Singleton();
}
return singleton;
}
}
}
fun main(){
println(Singleton.getInstance());
println(Singleton.getInstance());
}
The only difference between the previous and this example is that the companion object
is used inside a class. All the static variables and functions in Kotlin are placed inside the companion object
.
In this code, we have created a method named getInstance()
that ensures only one copy of the object is created. To ensure this is adhered to, we make the constructor()
private and ensure we check if an object exists before creating one.
Note that this approach works well, but it is not threaded safe. If an application is multithreaded, it can access the getInstance()
method simultaneously, leading to multiple objects that can lead to previously mentioned problems.
The next section shows how to make the singleton thread safe.
Run this code and ensure the output returns a reference to the same object shown below.
Singleton@1f32e575
Singleton@1f32e575
Create a Thread-Safe Singleton Using a Companion Object
Comment out the previous code and copy and paste the following code into the Main.kt
file after the comment.
class Singleton private constructor(){
companion object{
private var singleton = Singleton();
@Synchronized
fun getInstance(): Singleton{
if (singleton == null){
singleton = Singleton();
}
return singleton
}
}
}
Note that this code is similar to the previous code; the only difference is that we added the @Synchronized
annotation on the getInstance()
method.
This approach is referred to as a synchronized method in Java. Thread safety is realized through locks.
Objects usually have locks, and when a thread gets to the synchronized method, it acquires this lock exclusively until it has finished creating an object, which means no other thread can access this method.
When another thread accesses the lock, we already have an object, and the thread uses the existing thread without creating a new one. There are other ways to achieve synchronization, such as using the synchronized block, which helps us to synchronize a section of the code instead of the whole method.
Note that synchronization solves our problems but introduces performance issues in our application. Synchronization causes the performance of our application to reduce by a factor of 100
, and we should reconsider using it if our application is performance critical.
We can use approaches to shift from lazy initialization to eager initialization, double-checked locking, or let it remain if performance will not affect the application.
Create a Singleton Using the Lazy Initializer
Comment out the previous code and copy and paste the following code into the Main.kt
file after the comment.
class Singleton{
companion object{
val singleton: Singleton by lazy {
Singleton();
}
}
fun showMessage(): Singleton {
return Singleton.singleton
}
}
fun main(){
println(Singleton.singleton.showMessage())
println(Singleton.singleton.showMessage())
}
In this code, we have created a singleton using the function lazy()
that creates an instance of Lazy
that is thread-safe using the argument that we pass to it. It uses the LazyThreadSafetyMode.SYNCHRONIZED
to ensure that only one lock is used to initialize the lazy instance.
Run this code and ensure the output returns a reference to the same object shown below.
Singleton@3e3abc88
Singleton@3e3abc88
Conclusion
In this tutorial, we have learned the different ways we can use to create a singleton object in Kotlin. The approaches covered include using the object
keyword, the companion object
, and the lazy initializer
function.
Using the synchronized method, we have also learned how to ensure our singleton object is thread-safe when using a companion object
.
David is a back end developer with a major in computer science. He loves to solve problems using technology, learning new things, and making new friends. David is currently a technical writer who enjoys making hard concepts easier for other developers to understand and his work has been published on multiple sites.
LinkedIn GitHub