Kotlin lazy 委托的底层实现原理

代码概览

1
2
3
4
5
6
val lazyVal: String by lazy {
println("Computed")
"Lazy"
}
println(lazyVal) // 输出: Computed, Lazy
println(lazyVal) // 输出: Lazy(不再计算)
  • 功能lazyVal 使用 lazy 委托延迟初始化,只有首次访问时执行初始化块 { println("Computed"); "Lazy" },后续访问直接返回缓存值。
  • 输出
    • 第一次 println(lazyVal):打印 Computed(初始化执行),然后打印 Lazy
    • 第二次 println(lazyVal):直接打印 Lazy(无 Computed,因为值已缓存)。

底层实现原理

Kotlin 的 lazy 委托是标准库中的一种属性委托,通过 by lazy { ... } 实现延迟初始化。其底层依赖于 Lazy<T> 接口和线程安全的实现类(默认使用 SynchronizedLazyImpl)。以下是实现细节:

1. lazy 委托的定义

  • lazy 是一个高阶函数,定义在 Kotlin 标准库中:
    1
    public fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
    • 参数 initializer: () -> T 是一个无参 Lambda,返回类型为 T(此处 TString)。
    • 返回 Lazy<T> 接口实例,具体实现是 SynchronizedLazyImpl
  • Lazy<T> 接口:
    1
    2
    3
    4
    public interface Lazy<out T> {
    val value: T
    fun isInitialized(): Boolean
    }
    • value 属性获取委托值。
    • isInitialized() 检查是否已初始化。

2. 属性委托机制

  • by lazy { ... } 使用 Kotlin 的属性委托(Property Delegation):
    • lazy 函数返回的 Lazy<String> 对象实现了 getValue 方法,符合委托协议:

      1
      operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
    • 当访问 lazyVal 时,Kotlin 编译器将调用委托对象的 getValue,即 Lazyvalue 属性。

3. SynchronizedLazyImpl 的实现

  • lazy 默认使用 SynchronizedLazyImpl,其核心逻辑如下(简化伪代码):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    private class SynchronizedLazyImpl<out T>(private val initializer: () -> T) : Lazy<T> {
    @Volatile
    private var _value: Any? = UNINITIALIZED_VALUE // 初始标记
    private val lock = this // 使用自身作为锁

    override val value: T
    get() {
    if (_value === UNINITIALIZED_VALUE) {
    synchronized(lock) {
    if (_value === UNINITIALIZED_VALUE) {
    _value = initializer.invoke() // 执行初始化
    }
    }
    }
    @Suppress("UNCHECKED_CAST")
    return _value as T
    }

    override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
    }

  • 关键点

    • 延迟初始化
      • _value 初始为 UNINITIALIZED_VALUE(哨兵对象)。
      • 首次访问 value 时,检查 _value 是否未初始化,若是则调用 initializer()(即 { println("Computed"); "Lazy" })。
    • 线程安全
      • 使用 @Volatile 确保 _value 的可见性。
      • synchronized(lock) 实现双重检查锁(Double-Checked Locking),保证多线程环境下初始化只执行一次。
    • 缓存结果
      • 初始化后,_value 保存结果("Lazy"),后续访问直接返回,无需再次调用 initializer

4. 编译后的代码

  • Kotlin 编译器将 lazyVal 的访问转换为对 Lazy 对象的调用。简化后的字节码(伪代码):

    1
    2
    3
    4
    5
    6
    7
    // 编译前
    val lazyVal: String by lazy { println("Computed"); "Lazy" }

    // 编译后(大致等效)
    private val lazyVal$delegate: Lazy<String> = lazy { println("Computed"); "Lazy" }
    val lazyVal: String
    get() = lazyVal$delegate.value

  • 访问流程

    • 第一次 lazyVal 访问:
      • 调用 lazyVal$delegate.value
      • SynchronizedLazyImpl 执行 initializer,打印 Computed,返回 "Lazy",并缓存。
    • 第二次访问:
      • 直接返回缓存的 _value"Lazy"),无 initializer 调用。

代码执行流程

  1. 初始化
    • lazyVal 定义时,创建一个 SynchronizedLazyImpl 实例,保存 initializer = { println("Computed"); "Lazy" }
    • _value 初始为 UNINITIALIZED_VALUE
  2. 第一次访问
    • println(lazyVal) 触发 lazyVal$delegate.value
    • 检查 _value 未初始化,进入 synchronized 块。
    • 执行 initializer,打印 Computed,设置 _value = "Lazy"
    • 返回 "Lazy"println 输出 ComputedLazy
  3. 第二次访问
    • println(lazyVal) 再次调用 value
    • _value 已为 "Lazy",直接返回。
    • println 输出 Lazy,无 Computed

其他懒加载模式

  • lazy 支持不同线程安全模式,通过 LazyThreadSafetyMode 参数:
    1
    2
    val lazyVal: String by lazy(LazyThreadSafetyMode.NONE) { "Lazy" } // 无同步,单线程使用
    val lazyValPub: String by lazy(LazyThreadSafetyMode.PUBLICATION) { "Lazy" } // 允许多线程初始化,最终一致
  • 默认 SYNCHRONIZED(如上述代码)适合多线程场景。

代码示例(扩展)

1
2
3
4
5
6
7
8
9
10
11
val lazyVal: String by lazy {
println("Computed")
"Lazy"
}

fun main() {
println("Before access")
println(lazyVal) // 输出: Computed, Lazy
println(lazyVal) // 输出: Lazy
println("Is initialized: ${::lazyVal.isInitialized()}") // 输出: true
}
  • 输出

    1
    2
    3
    4
    5
    Before access
    Computed
    Lazy
    Lazy
    Is initialized: true

  • 说明

    • ::lazyVal.isInitialized() 检查是否已初始化,底层调用 lazyVal$delegate.isInitialized()
    • 确认第二次访问不触发 initializer

总结

  • 底层实现
    • lazy 委托通过 SynchronizedLazyImpl 实现延迟初始化。
    • 使用双重检查锁确保线程安全,首次访问执行 initializer,后续返回缓存值。
    • 编译器将 lazyVal 转换为 Lazy 对象的 value 访问。
  • 关键机制
    • 属性委托(by)将 get 操作转发给 Lazy
    • @Volatilesynchronized 保证多线程安全。
    • _value 缓存初始化结果,优化性能。
  • 实践
    • lazy 适合昂贵初始化的场景(如数据库连接、配置加载)。
    • 注意线程安全模式的选择(默认 SYNCHRONIZED 适合多数场景)。

通过 lazy 委托,Kotlin 提供了一种高效、线程安全的延迟初始化机制,广泛用于 Android 和其他 Kotlin 项目。

lazy委托和单例的区别

为什么要延迟初始化