Kotlin系列|一文看懂Lazy机制

概述

在实际开发我们经常会用到 lazy 懒加载,比如说:

private val manager by lazy {
    XxxManager()
}

private val manager by lazy(lock) {
    XxxManager()
}

private val manager by lazy(LazyThreadSafetyMode.XXX) {
    XxxManager()
}

来看看 lazy 对应的实现方式:

public actual fun <T> lazy(initializer: () -> T): Lazy<T> =
    SynchronizedLazyImpl(initializer)

public actual fun <T> lazy(lock: Any?, initializer: () -> T): Lazy<T> =
    SynchronizedLazyImpl(initializer, lock)

public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
    when (mode) {
        LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
        LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
        LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
    }

通过上面代码可以知道 lazy 实际上有三种实现方式:

  • LazyThreadSafetyMode.SYNCHRONIZED
  • LazyThreadSafetyMode.PUBLICATION
  • LazyThreadSafetyMode.NONE

Lazy 接口如下,by lazy 会委托到 value 上:

public interface Lazy<out T> {
    public val value: T

    public fun isInitialized(): Boolean
}

下面分别介绍一下这三种实现方式。

SYNCHRONIZED

SynchronizedLazyImpl

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    // 默认的初始化值
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    // 锁对象,默认使用自身实例
    private val lock = lock ?: this

    override val value: T
        get() {
            val _v1 = _value
            // 如果 value 不等于默认值,则说明已经初始化,直接返回
            if (_v1 !== UNINITIALIZED_VALUE) {
                return _v1 as T
            }
            // 初始化过程加 synchronized 锁
            return synchronized(lock) {
                val _v2 = _value
                // 再进行一次判断,已经初始化过则直接返回
                if (_v2 !== UNINITIALIZED_VALUE) {
                    _v2 as T
                } else {
                    // 初始化
                    val typedValue = initializer!!()
                    _value = typedValue
                    initializer = null
                    typedValue
                }
            }
        }
}

上面的注释有介绍这个流程,代码本身也很清晰。主要注意下面几个点:

  1. 当我们使用懒加载的 manager 对象时,实际上是调用了 Lazy.value,即会走上面的 get 方法。初始化过程通过 synchronized 来加锁,因此它是线程安全的。synchronized 经过多次迭代优化,已经不是当年那个重量级锁了(会经历锁升级过程),一般情况下还是比较轻量的,但在锁竞争激烈,锁持有时间长的时候(同时有多个线程使用这个 manager 实例,且初始化代码又比较耗时),会升级到重量级锁,经历用户态和内核态的切换,损耗性能。另外如果这个锁被某个子线程获取了,初始化方法又比较耗时(初始化逻辑无论在什么线程执行),此时主线程去使用这个 lazy 对象,就会陷入等待锁的过程。
  2. 上面在加锁后又判断了一次 _v2 是不是已经初始化过了,这是不是很像以前 Java 里双重检查的单例模式?原理其实也是类似的,不再赘述;另外 _valueVolatile 关键词,就如同 instanceVolatile 一样。
  3. 线程安全

PUBLICATION

SafePublicationLazyImpl

private class SafePublicationLazyImpl<out T>(initializer: () -> T) : Lazy<T>, Serializable {
    @Volatile private var initializer: (() -> T)? = initializer
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE

    override val value: T
        get() {
            val value = _value
            // 如果 value 不等于默认值,则说明已经初始化,直接返回
            if (value !== UNINITIALIZED_VALUE) {
                return value as T
            }

            val initializerValue = initializer
            if (initializerValue != null) {
                // 初始化
                val newValue = initializerValue()
                // 通过 CAS 比较 _value,如果等于 UNINITIALIZED_VALUE,则赋值为 newValue
                if (valueUpdater.compareAndSet(this, UNINITIALIZED_VALUE, newValue)) {
                    initializer = null
                    return newValue
                }
            }
            // 初始化函数为空,或者 compareAndSet 返回 false,说明已经赋值好了,直接返回
            return _value as T
        }

    companion object {
        private val valueUpdater = AtomicReferenceFieldUpdater.newUpdater(
            SafePublicationLazyImpl::class.java,
            Any::class.java,
            "_value" // fieldName
        )
    }
}

流程比较简单,可以直接看上面的注释。需要关注的点:

  1. Volatile 修饰的意义:保证可见性和有序性(禁止指令重排序),这部分不再赘述,属于老生常谈的东西了~
  2. AtomicReferenceFieldUpdater 中的 compareAndSet 方法会以原子操作去更新指定对象中的属性值,通过 CAS 方式,判断 _value 是否为 UNINITIALIZED_VALUE 值,如果是则将其更新为 newValue 并返回 true,否则不操作(说明已经被更新了),返回 false。
  3. 可以看出 initializerValue() 没有同步机制,初始化方法可能会被执行多次。
  4. 线程安全

NONE

UnsafeLazyImpl

internal class UnsafeLazyImpl<out T>(initializer: () -> T) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    private var _value: Any? = UNINITIALIZED_VALUE

    override val value: T
        get() {
            if (_value === UNINITIALIZED_VALUE) {
                _value = initializer!!()
                initializer = null
            }
            return _value as T
        }
}

这种方式最简单,就是在 get 的时候判断一下是否已经初始化,是则直接返回,否则初始化再返回。

没有任何线程安全的处理,因此它是线程不安全的,多线程调用下可能会多次初始化,导致逻辑异常。

总结

通过上面的分析,我们知道了这三种方式的特点和区别,值得注意的是,我们经常直接以 by lazy {} 的方式去使用延迟加载,根据源码可以知道这种方式是 SYNCHRONIZED 的,线程安全,默认参数大部分时候也是最适合的;另外 by lazy {} 会额外创建出一个 Lazy 实例和一个 lambda 对应的 Function 实例,在某些场景需要注意(性能)


SYNCHRONIZED:

  • 线程安全
  • 整个初始化过程都被 synchronized 包围,因此多线程下初始化函数不会执行多次,但首次获取到锁的线程可能会阻塞其他线程(对于主线程也要使用这个属性的场景,需要额外注意)。一般情况下 synchronized 也不重,可以放心使用,但在锁竞争激烈,锁持有时间长的时候,会升级到重量级锁,经历用户态和内核态的切换,损耗性能。
  • 如果没有并发操作,使用 synchronized 反而会多一点点加锁的消耗。

PUBLICATION

  • 线程安全
  • 多线程下初始化函数可能会被执行多次 ,但只有第一个初始化结果会被实际赋值,不影响使用。初始化函数不会阻塞其他线程,只有在赋值时才使用 CAS 机制
  • 这种方式虽然避免了 synchronized 同步,但可能会增加额外的工作量(初始化函数执行多次)。实际工作中我基本上没用过这种方式,但 Kotlin 提供了这个机制,我们在某些场景可以去权衡具体该使用谁,毕竟 synchronized 有膨胀的风险。

NONE

  • 非线程安全
  • 多线程调用下可能会多次初始化,导致逻辑异常
  • 没有并发场景时,性能最好。

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题
图片

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Kotlin中的lazy是一种以惰性方式初始化属性的机制。它可以在属性第一次使用或调用时进行初始化,而不是在定义属性时立即初始化。这种机制能够提高代码的效率,并且特别适用于那些在定义时无法确定初始值的情况,比如在Android中的生命周期驱动属性。使用lazy机制可以延迟属性的初始化,直到它真正被需要。 在Kotlin中,可以通过将属性声明为lazy来使用这种机制。使用lazy函数,我们可以定义一个lambda表达式来初始化属性。该lambda表达式在属性第一次被使用或调用时被执行,并且结果被缓存起来,以便之后的使用。这种机制确保了属性的初始化只会在需要时进行,而不会浪费不必要的早期初始化。 下面是使用lazy的示例代码: ```kotlin val myLazyProperty: String by lazy { // 这里是属性的初始化代码 // 可以是任何复杂的逻辑 "Initializing my lazy property" } fun main() { // 第一次使用属性,进行初始化 println(myLazyProperty) // 输出:Initializing my lazy property // 再次使用属性,不进行初始化,直接使用缓存的结果 println(myLazyProperty) // 输出:Initializing my lazy property } ``` 在上述示例中,myLazyProperty是一个使用lazy机制的属性。当第一次访问该属性时,lambda表达式会被执行,属性会被初始化为"Initializing my lazy property"。之后再次访问该属性时,不会再执行lambda表达式,而是直接使用已经缓存的结果。 总结起来,Kotlin中的lazy机制允许我们以惰性方式初始化属性,只有在需要时才进行初始化。这种机制可以提高代码的效率,并且特别适用于那些无法在定义时确定初始值的情况[1]。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值