Kotlin 之Lazy机制

79 阅读5分钟

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

private val testManager by lazy {
    TestManager()
}
private val testManager1 by lazy(LazyThreadSafetyMode.NONE) {
    TestManager()
}

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

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

/**
 * Creates a new instance of the [Lazy] that uses the specified initialization function [initializer]
 * and thread-safety [mode].
 *
 * If the initialization of a value throws an exception, it will attempt to reinitialize the value at next access.
 *
 * Note that when the [LazyThreadSafetyMode.SYNCHRONIZED] mode is specified the returned instance uses itself
 * to synchronize on. Do not synchronize from external code on the returned instance as it may cause accidental deadlock.
 * Also this behavior can be changed in the future.
 */
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)
    }

/**
 * Creates a new instance of the [Lazy] that uses the specified initialization function [initializer]
 * and the default thread-safety mode [LazyThreadSafetyMode.SYNCHRONIZED].
 *
 * If the initialization of a value throws an exception, it will attempt to reinitialize the value at next access.
 *
 * The returned instance uses the specified [lock] object to synchronize on.
 * When the [lock] is not specified the instance uses itself to synchronize on,
 * in this case do not synchronize from external code on the returned instance as it may cause accidental deadlock.
 * Also this behavior can be changed in the future.
 */
public actual fun <T> lazy(lock: Any?, initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer, lock)

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

LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)

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

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
}

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
    // final field is required to enable safe publication of constructed instance
    //锁对象,默认使用自身实例
    private val lock = lock ?: this

    override val value: T
        get() {
            val _v1 = _value
            //如果value不等于默认值,则说明已经初始化,直接返回
            if (_v1 !== UNINITIALIZED_VALUE) {
                @Suppress("UNCHECKED_CAST")
                return _v1 as T
            }
            //初始化过程加 synchronized锁
            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

    override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."

    private fun writeReplace(): Any = InitializedLazyImpl(value)
}

当我们使用懒加载的testManager对象时,实际上是调用了Lazy.value,即会走上面的get方法,初始化过程会通过synchronized来加锁,所以它是线程安全的。synchronized经过不断地优化,一般情况下是轻量级的锁了。但在锁竞争激烈,锁持有时间长的时候(也就是同时有多个线程使用这个testManager实例且初始化又比较耗时),会升级到重量级锁,损耗性能。如果这个锁被某个子线程获取了同时初始化方法又比较耗时,主线程需要使用lazy对象的话,就会陷入等待锁的过程。

上面在加锁后再进行了一次判断,这就很类似于Java里面的双重检查的单例模式,另外_value用关键字Volatile修饰也一样。

PUBLICATION

private class SafePublicationLazyImpl<out T>(initializer: () -> T) : Lazy<T>, Serializable {
    @Volatile private var initializer: (() -> T)? = initializer
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    // this final field is required to enable safe initialization of the constructed instance
    private val final: Any = UNINITIALIZED_VALUE

    override val value: T
        get() {
            val value = _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()
                //通过CAS比较_value,如果等于UNINITIALIZED_VALUE,则赋值为newValue
                if (valueUpdater.compareAndSet(this, UNINITIALIZED_VALUE, newValue)) {
                    initializer = null
                    return newValue
                }
            }
            //初始化函数为空,或者compareAndSet返回flase,说明已经赋值好了,直接返回
            @Suppress("UNCHECKED_CAST")
            return _value as T
        }

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

    override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."

    private fun writeReplace(): Any = InitializedLazyImpl(value)

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

可以看出initializerValue()没有同步机制,初始化方法可能会执行多次。

image.png

这个方法会以原子操作去更新指定对象的属性值,通过CAS方式去判断_value是否为UNINITIALZED_VALUE值,如果是则将其更新为newValue并返回true,否则不操作(说明已经被更新了)返回false。这个也是线程安全的。

NONE

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
            }
            @Suppress("UNCHECKED_CAST")
            return _value as T
        }

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

    override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."

    private fun writeReplace(): Any = InitializedLazyImpl(value)
}

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

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

总结: SYNCHRONIZED

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

PUBLICATION

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

NONE

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