Kotlin关键字by lazy中的Mode分析
class TestManager private constructor(){
companion object{
val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED){
TestManager()
}
}
}
上面代码是常见的Kotlin中通过by关键字来创建单例对象的实现,但lazy中的Mode是什么呢?
了解这些之前,我们先简单分别说下lazy和by,其中by是Kotlin中用于委托的关键字,而lazy是一个方法,其返回值是委托的具体对象。by lazy用于实现数据的惰性加载,在初始化一个常量时确保其不会被多次初始化。
LazyThreadSafetyMode的值有三个,通过它来确定对象的创建模式:
- LazyThreadSafetyMode.SYNCHRONIZED
- LazyThreadSafetyMode.PUBLICATION
- LazyThreadSafetyMode.NONE
lazy方法默认是使用SynchronizedLazyImpl来返回委托的具体对象的。
Lazy实现了by lazy的方法:
@kotlin.internal.InlineOnly
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
也就是我们的获取到的值就是这个value,那么我们就要来看这个value是怎么获取到的。分析三种Mode下value不同取值,可以确定后续我们在使用by lazy时应该使用那种Mode,首先先看SynchronizedLazyImpl的实现:
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
//lazy后的方法体,返回当前需要创建的对象
private var initializer: (() -> T)? = initializer
//默认值,定义了一个未初始化的值,为了解决DCL带来指令重排序导致主存和工作内存数据不一致的问题,这里使用Volatile原语注解,线程安全
@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
//获取value的值就是通过get方法
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 {
//如果没有初始化,那么通过initializer来进行初始化操作
val typedValue = initializer!!()
//并将这个值保存在_value中
_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)
}
SafePublicationLazyImpl的实现:
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()
//通过CAS保证只有在原始值为UNINITIALIZED_VALUE时赋值
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中的原子操作
java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater(
SafePublicationLazyImpl::class.java,
Any::class.java,
"_value"
)
}
}
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
}
@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)
}
经过上面的源码分析,我们不难看出三种Mode的区别:
- LazyThreadSafetyMode.SYNCHRONIZED 通过synchronized关键值创建同步锁来实现线程安全
- LazyThreadSafetyMode.PUBLICATION 通过CAS来保证线程安全,多线程的情况下初始化方法会调用,但只有一个生效。
- LazyThreadSafetyMode.NONE,只要标识为未初始化就调用初始化来赋值,线程不安全。
所以经过上面的分析,在确定当前属性不会在多个线程之间共享的时候,则可以使用NONE模式,这是最快的模式,但也是最不安全的模式。
如果属性是要在多个线程间共享,那么使用SYNCHRONIZED和PUBLICATION,但因为SYNCHRONIZED引入了锁的机制,它是最安全的,但也是最慢的模式。折中的话则可以使用PUBLICATION。