kotlin—lazy及其原理
1、lazy简介
lazy是属性委托的一种,是有kotlin标准库实现。它是属性懒加载的一种实现方式,在对属性使用时才对属性进行初始化,并且支持对属性初始化的操作时进行加锁,使属性的初始化在多线程环境下线程安全。lazy默认是线程安全的。
2、语法
lazy既然是属性委托的一种,那么其语法也遵循属性委托的语法:
var/val propertyName [:Type] by express
对应的lazy 的语法为:
val propertyName by lazy(lock: Any?, initializer: () -> T)
或者 val propertyName by lazy(mode: LazyThreadSafetyMode, initializer: () -> T)
或者 val propertyName by lazy(initializer: () -> T)
由于lazy为函数,其最后一个参数是函数,那么可以使用lamda的语法代替最后一个函数参数:
val propertyName by lazy(lock: Any?) {
...
T
}
或者 val propertyName by lazy(mode: LazyThreadSafetyMode) {
...
T
}
或者 val propertyName by lazy {
...
T
}
3、lazy原理
通过2中的语法,我们知道lazy是kotlin标准库中的重载函数,我们先从标准库lazy的函数的分析其原理:
//默认的实现方式——传入初始化函数,创建同步懒加载实例对象SynchronizedLazyImpl并作为函数返回
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
//mode是选择线程安全模式,initializer是初始化函数,返回Lazy泛型
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
when (mode) {
//如果线程安全模式是同步模式,则创建同步懒加载实例对象SynchronizedLazyImpl并作为函数返回
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
//如果线程安全模式是公有模式,则创建公有模式懒加载实例对象SafePublicationLazyImpl并作为函数返回
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
//如果是无线程安全模式,则创建非安全模式懒加载实例对象UnsafeLazyImpl并作为函数返回
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
}
//lock是初始化时使用的所对象,initializer是初始化函数,创建同步懒加载实例对象SynchronizedLazyImpl并作为函数返回
public actual fun <T> lazy(lock: Any?, initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer, lock)
lazy函数默认情况下是同步安全锁模式,其可以指定线程同步模式、线程公有模式、非线程安全模式,也在同步模式时指定使用的锁对象,lazy函数会创建懒加载类的实现类,通过懒加载类的实现类实现不同模式的懒加载。
我们依次分析SynchronizedLazyImpl、SafePublicationLazyImpl、UnsafeLazyImpl这三种模式是怎么实现懒加载的:
3.1 SynchronizedLazyImpl
SynchronizedLazyImpl是同步模式的懒加载,它是lazy的默认实现,其在多线程环境下进行初始化是线程安全的,我们看看其源码实现:
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
//设置初始化函数
private var initializer: (() -> T)? = initializer
//定义属性的私有真实值,供内部成员value调用,赋予初值为未指定UNINITIALIZED_VALUE
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required to enable safe publication of constructed instance
//设置同步使用的锁底下,如果参数lock为空则使用当前对象this
private val lock = lock ?: this
//定义属性的公有值,其取的值来源私有值_value,供外部使用
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的实现原理其实是使用两个属性,一个是公有属性value—对外代表属性的值,一个是私有属性_value——是真正的值。value的get内部对_value进行初始化,如果_value已初始化则直接返回,如果没有初始化过则加锁并调用初始化函数把返回值赋值给_value。
3.2、SafePublicationLazyImpl
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()
//使用cms自旋锁
if (valueUpdater.compareAndSet(this, UNINITIALIZED_VALUE, newValue)) {
//如果未初始化过,则赋值给真实值_value,并返回初始化值
initializer = null
return newValue
}
}
@Suppress("UNCHECKED_CAST")
return _value as T
}
//省略....
companion object {
//创建对_value的原子操作类AtomicReferenceFieldUpdater,采用cms自旋锁
private val valueUpdater = java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater(
SafePublicationLazyImpl::class.java,
Any::class.java,
"_value"
)
}
}
公共线程安全模式SafePublicationLazyImpl与同步模式SynchronizedLazyImpl的区别在于,SafePublicationLazyImpl使用自旋锁进行初始化操作,而SynchronizedLazyImpl是要同步锁的方式进行初始化操作,其他与SynchronizedLazyImpl的实现一样。
3.3、SafePublicationLazyImpl
SafePublicationLazyImpl是非线程安全的懒加载实现模式,在单线程下进行初始化是没啥问题,但是多线程下是进行初始化是不安全的,我们从其源码分析其原理:
nternal 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;如果初始化函数为空则抛出异常
_value = initializer!!()
initializer = null
}
@Suppress("UNCHECKED_CAST")
return _value as T
}
//省略.....
}
SafePublicationLazyImpl与前面的两种模式的实现方式不一样就是初始化时没有加任何锁,其它是一样的。
3.4、字节码分析lazy的原理
上面分析了使用lazy函数之后返回了不同的懒加载实现类及各懒加载实现类的原理,所以lazy的语句最终语句的是:
val propertyName by [SynchronizedLazyImpl | SafePublicationLazyImpl | UnsafeLazyImpl]
但具体是怎么个懒加载实现类的value的get方法呢?——下面我们举例,然后通过编译后的字节码分析器实现原理:
举例:
class DelegateByLazy {
val value by lazy { 20 }
}
编译后的字节码文件:
public final class com/wyx/tcanvas/test/delegate/DelegateByLazy {
// compiled from: DelegateByLazy.kt
//省略......
// access flags 0x18
final static INNERCLASS com/wyx/tcanvas/test/delegate/DelegateByLazy$value$2 null null
// access flags 0x12
//自动生成懒加载类实例对象,及委托对象value$delegate
private final Lkotlin/Lazy; value$delegate
@Lorg/jetbrains/annotations/NotNull;() // invisible
// access flags 0x1
//类实例对象的初始化函数
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
//调用父类Object的init方法
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 4 L1
ALOAD 0
GETSTATIC com/wyx/tcanvas/test/delegate/DelegateByLazy$value$2.INSTANCE : Lcom/wyx/tcanvas/test/delegate/DelegateByLazy$value$2;
//校验初始化函数的类型是否为函数类型
CHECKCAST kotlin/jvm/functions/Function0
//调用lazy函数,参数为初始化函数,将返回值即懒加载实现类放入栈顶
INVOKESTATIC kotlin/LazyKt.lazy (Lkotlin/jvm/functions/Function0;)Lkotlin/Lazy;
//将栈顶的值即懒加载实现类赋值给委托对象value$delegate
PUTFIELD com/wyx/tcanvas/test/delegate/DelegateByLazy.value$delegate : Lkotlin/Lazy;
L2
LINENUMBER 3 L2
RETURN
//省略......
// access flags 0x11
//获取value的值的函数
public final getValue()I
L0
LINENUMBER 4 L0
ALOAD 0
//载入this.value$delegate放入栈顶
GETFIELD com/wyx/tcanvas/test/delegate/DelegateByLazy.value$delegate : Lkotlin/Lazy;
//省略....
//调用this.value$delegate.getValue
INVOKEINTERFACE kotlin/Lazy.getValue ()Ljava/lang/Object; (itf)
CHECKCAST java/lang/Number
//将返回的值转为Int类型并放到栈顶
INVOKEVIRTUAL java/lang/Number.intValue ()I
L1
LINENUMBER 4 L1
IRETURN //返回栈顶的值
//省略......
}
通过对生成的字节码的分析,lazy的原理:
- 自动生成一个委托对象,类型是懒加载类
- 在类实例对象初始化init函数中调用lazy函数,lazy函数创建具体的懒加载实现类,默认是SynchronizedLazyImpl,并将其赋值给委托对象
- 消除被委托属性的定义,而是自动生成被委托属性的get方法
- 被委托属性的get方法内部是调用委托对象的getValue方法
- 委托对象的getValue方法内部判断真实值_value是否初始化过,如果初始化过则直接返回,如果没有初始化过,根据采用的线程安全模式使用不同的锁或者不使用锁进行初始化
注意lazy实现了懒加载,达到在使用时才进行初始化的目的,但是也为此增加了一个懒加载类,如果一个类的初始化操作不耗时却使用lazy进行懒加载是不明智的,lazy的适合场景是类的初始化操作比较耗时占资源。