lazy关键字
前言
随着kotlin在Android日常开发中越来越频繁地使用,笔者觉得有必要对kotlin的一些特性做一些原理的梳理,所谓知其然知其所以然,才能更好让kotlin服务于我们的业务开发,所以会做一个kotlin系列,每个知识点简短,像个小卡片一样,便于理解和快速回顾。所有的源码为:Kotlin 标准库版本为1.9.10。
使用
- 一般用法:使用默认的线程安全模式创建lazy对象
private val lazyObj by lazy {
LazyObject()
}
- 进阶用法
- 自定义创建对象使用的锁
// 自定义锁 private val lock = object : Any() {} private val lazyObj by lazy(lock) { ... }- 自定义初始化模式
// default,等价于 by lazy { ... } private val lazyObj by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { ... } private val lazyObj by lazy(LazyThreadSafetyMode.PUBLICATION) { ... } private val lazyObj by lazy(LazyThreadSafetyMode.NONE) { ... }
原理
默认的lazy初始化模式
通过使用属性委托,将optionalObj对象委托给lazy函数实现。lazy函数是一个分平台实现的接口,在Android开发中,我们使用的JVM的实现,文件名称为LazyJVM
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
从上可以看到默认的lazy使用的是SYNCHRONIZED模式,真正实现lazy对象创建的是SynchronizedLazyImpl函数。
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)
}
模式说明
- LazyThreadSafetyMode.SYNCHRONIZED
同步模式。只允许一个线程执行初始化,线程安全,高并发性能可能下降。
- LazyThreadSafetyMode.PUBLICATION
发布模式。初始化后发布给其他线程,多线程间共享,但存在线程安全问题。
- LazyThreadSafetyMode.NONE
非同步模式。高性能,不使用任何锁,非线程安全,多线程有线程安全问题。
SYNCHRONIZED模式源码分析
从代码实现中,我们可以知道:
- lazy接口实现默认为线程安全,默认使用本身作为同步锁,支持外部定制锁。
- 使用_value后备属性作为cache值存放的位置,且使用@Volatile修饰来保证线程间的可见性
- 在_value对象完成初始化后,会释放用于初始化的initializer接口,避免内存泄漏和减少内存占用。
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
override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
private fun writeReplace(): Any = InitializedLazyImpl(value)
}
PUBLICATION模式源码分析
NONE模式较好理解,感兴趣的自行翻看源码。PUBLICATION模式我们一起来看下源码的实现:
从源码实现上,我们发现:
initializer和_value都使用@Volatile修饰,以达成线程间可见的目的,也就是所谓的发布给其他线程的效果。对比SYNCHRONIZED模式则没有使用@Volatile修饰initializer参数。- 在PUBLICATION模式中使用的CAS操作来实现线程安全的操作,是一种无锁算法,所以它的性能会更优于于SYNCHRONIZED模式
- PUBLICATION模式只保证线程安全的操作和线程间可见,所以在多线程时会存在线程安全问题:例如,当一个线程正在执行initializer函数计算Lazy属性的值时,另一个线程可能会看到_value已经被设置为非null值,但是initializer函数还没有执行完毕,导致其他线程访问到的Lazy属性的值是不完整的。
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
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
}
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"
)
}
}