这是为你整理的最终技术文档。文档聚焦于 Kotlin 顶级函数实现 DCL 单例的四种演进方案,重点标注了面试中必须指出的致命错误。
Kotlin 单例模式(DCL)最终技术文档
核心背景
在禁用 object 和 companion object 的情况下,必须使用 Top-level(顶级) 变量与函数。此时,变量编译后等同于 Java 的 static 成员。
情况一:❌ 致命错误版(最常见的挂点)
1 | private var instance: Singleton? = null // 错误 1:缺失 @Volatile |
致命缺陷指出:
- 指令重排风险:缺少
@Volatile,导致线程 A 可能先赋值引用,后执行构造函数。线程 B 在锁外检查到instance非空,拿走一个未初始化的“半成品”对象,引发崩溃。 - 同步失效:
synchronized(Any())导致每个线程都在拿不同的钥匙,无法形成互斥,会创建出多个单例。
情况二:❌ 性能低效版(判空仅在锁内)
1 |
|
缺陷指出:
- 性能瓶颈:虽然线程安全,但由于没有外部判空,每一次调用该方法都要排队竞争锁。在高并发场景(如 Android 列表滑动、数据库频繁读写)下会造成严重卡顿。
情况三:❌ 锁范围错误版(成员变量锁)
1 | class Singleton private constructor() { |
缺陷指出:
- 作用域冲突:顶级函数(静态上下文)无法访问类内部的成员变量
lock。 - 逻辑悖论:单例创建前实例并不存在,实例不存在则
lock不存在。必须使用静态锁才能保证创建过程的唯一性。
情况四:✅ 最终正确版(标准 DCL)
1 | // 必须:禁止指令重排序,保证锁外线程安全 |
💡 面试核心知识点总结
| 关键组件 | 核心作用 |
|---|---|
@Volatile |
保护锁外线程。防止其读取到“已赋值地址但未初始化完成”的对象(解决指令重排)。 |
| 静态唯一锁 | 保证互斥性。确保所有线程竞争同一把钥匙,防止创建多个实例。 |
| 外层判空 | 性能优化。单例一旦创建,后续调用实现“零开销”返回。 |
| 内层判空 | 原子性保障。拦截那些在锁外排队的线程,确保只执行一次构造函数。 |