Kotlin双重校验获取单例问题

在 Kotlin 中,synchronized(Singleton::class.java) 和 synchronized(Singleton::class) 的锁对象本质不同,这会导致同步行为差异。以下是详细对比和结论:

  1. 本质区别
    表达式 锁对象类型 实际锁对象 线程同步范围
    synchronized(Singleton::class) Kotlin 的 KClass Singleton 的 KClass 实例 仅 Kotlin 侧代码
    synchronized(Singleton::class.java) Java 的 Class 对象 Singleton 的 Java 类对象 全平台(Java/Kotlin)
    关键结论:
    Singleton::class 获取的是 Kotlin 的 KClass 反射对象,与 Java 的 Class 对象不是同一个实例。
    Singleton::class.java 是通过 KClass.java 属性显式获取 Java 的 Class 对象,与 Java 中的 Singleton.class 完全等价。
  2. 双重校验锁的陷阱
    若错误使用 synchronized(Singleton::class) 实现单例:

Kotlin
class Singleton private constructor() {
companion object {
@Volatile private var instance: Singleton? = null

    fun getInstance(): Singleton {
        return instance ?: synchronized(Singleton::class) { // 锁的是 KClass,非 Java Class
            instance ?: Singleton().also { instance = it }
        }
    }
}

}
问题表现:
初始化多次:多个线程可能获取不同的锁对象(KClass 与 Java Class),导致同步失效。
与 Java 代码不兼容:Java 代码中若使用 synchronized(Singleton.class) 锁住同一单例,会与 Kotlin 的 KClass 锁形成两把不同的锁(破坏原子性)。

  1. 正确实践
    (1) 统一使用 Java 的 Class 对象
    Kotlin
    synchronized(Singleton::class.java) { // 明确锁定 Java 类对象
    // 双重校验逻辑
    }
    优势:
    与 Java 的 synchronized(Singleton.class) 完全等价,确保跨语言同步一致性。
    兼容 JVM 的类锁机制,避免类初始化冲突。
    (2) 优先使用 伴生对象实例 作为锁
    Kotlin
    synchronized(this) { // this 指向 companion object 实例
    // 双重校验逻辑
    }
    优势:
    缩小锁范围,避免类锁的全局竞争。
    彻底消除与 Java 类锁的潜在冲突。
  2. 字节码验证
    编译以下代码:

Kotlin
fun testLock() {
synchronized(Singleton::class) { /* ... / }
synchronized(Singleton::class.java) { /
... */ }
}
反编译后的 Java 代码:
Java
public final void testLock() {
// synchronized(Singleton::class)
synchronized (Singleton.class) { // 实际锁的是 Singleton 的 KClass 包装类
// ...
}

// synchronized(Singleton::class.java)
synchronized (Singleton.class) { // 直接锁 Java 的 Class 对象
    // ...
}

}
陷阱:Kotlin 的 synchronized(Singleton::class) 实际编译为 synchronized (Singleton.class),但此处的 Singleton.class 是 Kotlin 生成的 KClass 封装类(如 Singleton.class 可能是 Singleton$Companion.class),而非原始 Java 类对象。

  1. 总结
    场景 推荐锁对象 原因
    纯 Kotlin 单例 synchronized(companion object) 缩小锁粒度,避免类锁副作用
    跨 Java/Kotlin 的单例 synchronized(Singleton::class.java) 确保与 Java 类锁一致,全平台兼容
    高频初始化场景 优先 Lazy 委托 避免手动管理锁,代码简洁且线程安全
    永远避免 synchronized(Singleton::class),因其实际锁对象可能不符合预期,破坏双重校验锁的原子性。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容