I have been looking at some Google sample code and they seem to create a singleton using the the following code:
companion object {
// For Singleton instantiation
@Volatile
private var instance: CarRepository? = null
fun getInstance(carDao: CarDao) =
instance ?: synchronized(this) {
instance ?: CarRepository(carDao).also { instance = it }
}
}
So I know @Volatile
means that
Marks the JVM backing field of the annotated property as volatile, meaning that writes to this field are immediately made visible to other threads.
Should all Singletons instances always be marked as @Volatile
? If so, why?
Lastly, I don't understand the getInstance
function
instance ?: synchronized(this) {
instance ?: CarRepository(carDao).also { instance = it }
}
What is it exactly doing here?
UPDATE:
Source: Google's Sunflower
I changed the Repository and Dao name for my own use, but it is the same logic in the Repository
files.
By using the keyword object in your app, you're defining a singleton. A singleton is a design pattern in which a given class has only one single instance inside the entire app. A singleton's two most common use cases are: To share data between two otherwise unrelated areas of your project.
In Kotlin, the singleton pattern is used as a replacement for static members and fields that don't exist in that programming language. A singleton is created by simply declaring an object . Contrary to a class , an object can't have any constructor, but init blocks are allowed if some initialization code is needed.
Singleton Pattern ensures that only one instance would be created and it would act as a single point of access thereby ensuring thread safety. But the above codes are dangerous, especially if it's used in different threads. If two threads access this singleton at a time, two instances of this object could be generated.
In short, companion objects are singleton objects whose properties and functions are tied to a class but not to the instance of that class — basically like the “static” keyword in Java but with a twist.
There's a great answer here for why the field should be volatile. Essentially, without it, it's possible for one thread to get a reference to the instance before it has been fully constructed.
For the getInstance()
function, you have:
instance ?:
This means that the method will return instance
if it's not null, otherwise it will execute the right side of the ?:
.
synchronized(this) {
instance ?:
}
Similarly here, after the first check for whether or not the instance is null, after synchronizing on the class (the companion object
) it again checks for a non-null value and returns it if available, before executing the last command:
CarRepository(carDao).also { instance = it }
This initializes a new CarRepository
and then using the .also
block, assigns it
(the CarRepository
) to the instance
field before returning. It's a bit confusing just because the entire statment is an expression. If you make this much more verbose it might look like:
fun getInstance(carDao: CarDao): CarRepository {
var cachedInstance = instance
if (cachedInstance != null) {
return cachedInstance
}
synchronized(this) {
cachedInstance = instance
if (cachedInstance == null) {
cachedInstance = CarRepository(carDao)
instance = cachedInstance
}
return cachedInstance
}
}
As a word of warning I'm not really convinced this particular example is a good pattern to follow. For example, consider the following:
val carDao1 = CarDaoImpl1()
val carDao2 = CarDaoImpl2()
val carRepo1 = CarRepository.getInstance(carDao1)
val carRepo2 = CarRepository.getInstance(carDao2)
// carRepo2 actually points to carDao1!
Even though this isn't a real singleton, I will try to explain what's exactly going on with comments:
fun getInstance(carDao: CarDao) =
/* if the instance is not null, just return it: */
instance ?:
/* instance is null... enter synchronized block for the first thread...
all other threads entering here while the first one is still not finished will block then */
synchronized(this) {
/* now the next line is actually here for all the blocked threads... as soon as they are released, they should take the instance that was set by the first thread */
instance ?:
/* the next line actually is only executed by the first thread entering the synchronized-block */
CarRepository(carDao).also {
/* and this sets the instance that finally is returned by all others */
instance = it }
}
Regarding the @Volatile
... well... that's here so that the instance variable actually gets synchronized between the threads then... so that it is available when the first thread returns and the other continue entering the synchronized-block.
Now after the explanation: for a Kotlin way to write singletons check the Kotlin reference regarding Object Expressions, Object Declarations and Companion Objects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With