美文网首页DevSupport
by lazy是如何实现延迟加载的

by lazy是如何实现延迟加载的

作者: aaa | 来源:发表于2019-03-17 21:15 被阅读0次

    首先我们来看lazy的函数声明,方法的参数为一个返回值为泛型T的函数,返回值同样是一个含有泛型TLazy对象

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

    那么,Lazy是什么呢,我们来看一下它的定义

    public interface Lazy<out T> {
        /**
         * Gets the lazily initialized value of the current Lazy instance.
         * Once the value was initialized it must not change during the rest of lifetime of this Lazy instance.
         */
        public val value: T
    
        /**
         * Returns `true` if a value for this Lazy instance has been already initialized, and `false` otherwise.
         * Once this function has returned `true` it stays `true` for the rest of lifetime of this Lazy instance.
         */
        public fun isInitialized(): Boolean
    }
    

    注释中已经写得很清楚了,Lazy是一个接口,其中包括一个值value,即我们在by lazy{}中的返回值和一个用来判断当前值是否已经被初始化过的方法isInitialized()

    然后让我们回到lazy方法,lazy方法的实现很简单,就是返回了一个SynchronizedLazyImpl的对象,下面让我们来分析一下这个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
        // final field is required to enable safe publication of constructed instance
        private val lock = lock ?: this
    
        override val value: T
            get() {
                val _v1 = _value
                if (_v1 !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST")
                    return _v1 as T
                }
    
                return synchronized(lock) {
                    val _v2 = _value
                    if (_v2 !== UNINITIALIZED_VALUE) {
                        @Suppress("UNCHECKED_CAST") (_v2 as T)
                    } else {
                        val typedValue = initializer!!()
                        _value = typedValue
                        initializer = null
                        typedValue
                    }
                }
            }
    
        override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
    }
    

    分析代码我们可以发现,SynchronizedLazyImpl实现了Lazy接口,同时接收两个参数,一个是by lazy{}中传进来的闭包,另一个是任意类型的对象,默认为null,然后在类中用一个变量initializer来保存构造方法中传入的闭包,并给_value赋了一个初始值UNINITIALIZED_VALUE,这个值就仅仅是一个空对象,什么都没有实现。之后对锁对象进行了赋值,当构造方法中传入的锁为空时,将锁的值赋为当前对象。以上就是对SynchronizedLazyImpl中成员变量的分析,分析完这些我们就可以来看看它是究竟如何实现延迟加载的了。

    SynchronizedLazyImplLazy中的value属性进行了进行了覆写,修改了它的get()方法,让我们来逐段分析

                val _v1 = _value
                if (_v1 !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST")
                    return _v1 as T
                }
    

    这一段含义就是用_valueUNINITIALIZED_VALUE的值作对比,如果这两个值不相同,就说明当前的值已经被加载过了,直接返回即可,不用再去走下边的初始化操作。
    否则的话就要执行以下的代码

                return synchronized(lock) {
                    val _v2 = _value
                    if (_v2 !== UNINITIALIZED_VALUE) {
                        @Suppress("UNCHECKED_CAST") (_v2 as T)
                    } else {
                        val typedValue = initializer!!()
                        _value = typedValue
                        initializer = null
                        typedValue
                    }
                }
    

    可以看到这段代码逻辑中,先是对返回值加了锁,然后又去使_valueUNINITIALIZED_VALUE的值作对比,这么做的原因是确保在多线程的环境下只有一个线程来对该值做初始化,与DCL单例的思路一致。else语句中则是真正的初始化逻辑,就是把闭包执行了一遍并将返回值返回。

    以上我们就分析完了by lazy{}是如何实现延迟加载的。

    其实,除了默认的实现方式,kotlin还为我们提供了其他的几种实现方式,根据是否线程安全可以分三种,以一个枚举类的形式表现。

    public enum class LazyThreadSafetyMode {
    
        /**
         * Locks are used to ensure that only a single thread can initialize the [Lazy] instance.
         */
        SYNCHRONIZED,
    
        /**
         * Initializer function can be called several times on concurrent access to uninitialized [Lazy] instance value,
         * but only the first returned value will be used as the value of [Lazy] instance.
         */
        PUBLICATION,
    
        /**
         * No locks are used to synchronize an access to the [Lazy] instance value; if the instance is accessed from multiple threads, its behavior is undefined.
         *
         * This mode should not be used unless the [Lazy] instance is guaranteed never to be initialized from more than one thread.
         */
        NONE,
    }
    

    注释中写得很清楚,SYNCHRONIZED就是by lazy{}中默认使用的,通过加锁的方式来保证线程安全,而PUBLICATION是初始化方法可以被多次调用,但是值只是第一次返回时的返回值。NONE就是不加锁。
    我们可以在调用lazy的时候传入枚举值来构建不同的Lazy对象。

    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)
        }
    
    

    下面我们来看一下SafePublicationLazyImplUnsafeLazyImpl的实现,其他的地方都和SynchronizedLazyImpl一致,主要还是看valueget()方法

        override val value: T
            get() {
                val value = _value
                if (value !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST")
                    return value as T
                }
    
                val initializerValue = initializer
                // if we see null in initializer here, it means that the value is already set by another thread
                if (initializerValue != null) {
                    val newValue = initializerValue()
                    if (valueUpdater.compareAndSet(this, UNINITIALIZED_VALUE, newValue)) {
                        initializer = null
                        return newValue
                    }
                }
                @Suppress("UNCHECKED_CAST")
                return _value as T
            }
    

    大体上逻辑与SynchronizedLazyImpl一致,唯一一点有不同的地方就是在设置值的时候是用了一个valueUpdater.compareAndSet(this, UNINITIALIZED_VALUE, newValue)来给_value赋值,这里的valueUpdater是一个原子属性更新器,具体的声明如下所示,感兴趣的读者可以自行查阅。

            private val valueUpdater = java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater(
                SafePublicationLazyImpl::class.java,
                Any::class.java,
                "_value"
            )
    

    通过这种方式就达到了只有第一次的返回值可以赋值给_value的效果。

    随后我们来看UnsafeLazyImpl,它的实现就更为简单,就仅仅是SafePublicationLazyImpl的不加锁版本

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

    以上我们就完成了对kotlin中Lazy的大部分内容的分析

    相关文章

      网友评论

        本文标题:by lazy是如何实现延迟加载的

        本文链接:https://www.haomeiwen.com/subject/gpcpmqtx.html