Search code examples
multithreadingkotlinthread-safetyvolatiledouble-checked-locking

Can a @Volatile lateinit var be atomically initialized using a DCL pattern in Kotlin?


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?


Solution

  • 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.