概述

在实践开发咱们经常会用到 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 相同。假如有疑问能够参阅 Java 单例模式。
  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

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

文中内容如有过错欢迎指出,共同进步!更新不易,觉得不错的留个再走哈~


Android视图体系:Android 视图体系相关的底层原理解析,看完定有收成。

Kotlin专栏:Kotlin 学习相关的博客,包括协程, Flow 等。

Android架构学习之路:架构不是一蹴而就的,希望咱们有一天的时分,能够从自己写的代码中找到架构的成就感,而不是干几票就跑路。工作太忙,更新比较慢,大家有兴趣的话能够一起学习。

Android实战系列:记载实践开发中遇到和解决的一些问题。

Android优化系列:记载Android优化相关的文章,持续更新。