请考虑以下用 Kotlin 编写的 DCL 示例:
@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!!
}
它与二十年前讨论的 Java 对应部分非常相似,当时 Java 内存模型随着从 Java 1.4 到 Java 5 的过渡而得到修正。话虽如此,我完全确信上述代码片段在 JVM 上运行时会正常运行。
现在,让我们用lateinit var
:
@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
}
它在语义上与原始示例等同吗?它是否保持相同的原子性和可见性保证?
这两个代码片段编译为几乎相同的字节码。
lateinit
属性只是被编译成常规 Java 字段,并且isInitialized
基本上是一个空检查。唯一的区别是最后一条
return
语句。访问未初始化的lateinit
属性将抛出UninitializedPropertyAccessException
,而当为 null 时property!!
将抛出。当然,这种差异与是否具有相同的线程安全保证无关。NullPointerException
property
话虽如此,Kotlin/JVM 规范尚未出台,Kotlin/Core 规范中也未提及任何有关并发的内容。因此严格来说,Kotlin 代码如何编译成字节码几乎没有保证。