在 Kotlin 中,synchronized(Singleton::class.java) 和 synchronized(Singleton::class) 的锁对象本质不同,这会导致同步行为差异。以下是详细对比和结论:
- 本质区别
表达式 锁对象类型 实际锁对象 线程同步范围
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 完全等价。 - 双重校验锁的陷阱
若错误使用 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) 统一使用 Java 的 Class 对象
Kotlin
synchronized(Singleton::class.java) { // 明确锁定 Java 类对象
// 双重校验逻辑
}
优势:
与 Java 的 synchronized(Singleton.class) 完全等价,确保跨语言同步一致性。
兼容 JVM 的类锁机制,避免类初始化冲突。
(2) 优先使用 伴生对象实例 作为锁
Kotlin
synchronized(this) { // this 指向 companion object 实例
// 双重校验逻辑
}
优势:
缩小锁范围,避免类锁的全局竞争。
彻底消除与 Java 类锁的潜在冲突。 - 字节码验证
编译以下代码:
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 类对象。
- 总结
场景 推荐锁对象 原因
纯 Kotlin 单例 synchronized(companion object) 缩小锁粒度,避免类锁副作用
跨 Java/Kotlin 的单例 synchronized(Singleton::class.java) 确保与 Java 类锁一致,全平台兼容
高频初始化场景 优先 Lazy 委托 避免手动管理锁,代码简洁且线程安全
永远避免 synchronized(Singleton::class),因其实际锁对象可能不符合预期,破坏双重校验锁的原子性。