Consider the following DCL example written in Kotlin:
@Volatile
private var property: String? = null
private val lock = ReentrantLock()
fun singletonValue(): String {
if (property == null) {
lock.withLock {
if (property == null) {
property = "Hello World"
}
}
}
return property!!
}
It's pretty much the same as its Java counterpart discussed like two decades ago, when Java Memory Model was corrected with the transition from Java 1.4 to Java 5. Having said that, I'm perfectly sure the above code fragment will behave correctly when run on the JVM.
Now, let's use a lateinit var
instead:
@Volatile
private lateinit var property: String
private val lock = ReentrantLock()
fun singletonValue(): String {
if (!::property.isInitialized) {
lock.withLock {
if (!::property.isInitialized) {
property = "Hello World"
}
}
}
return property
}
Is it semantically equivalent to the original example? Does it maintain the same atomicity and visibility guarantees?
These two code snippets compile to the almost same bytecode. lateinit
properties are just compiled into regular Java fields, and isInitialized
is basically a null check.
The only difference is the last return
statement. An access to an uninitialised lateinit
property will throw a UninitializedPropertyAccessException
, whereas property!!
will throw a NullPointerException
when property
is null. Of course, this difference is irrelevant to whether the same thread safety guarantees hold.
That said, there is no Kotlin/JVM specification yet, and the Kotlin/Core specification doesn't mention anything about concurrency. So strictly speaking, there is little guarantee about how your Kotlin code is going to be compiled into bytecode.