Kotlin

kotlin—lazy及其原理

2022-05-19  本文已影响0人  jxiang112

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的原理:

注意lazy实现了懒加载,达到在使用时才进行初始化的目的,但是也为此增加了一个懒加载类,如果一个类的初始化操作不耗时却使用lazy进行懒加载是不明智的,lazy的适合场景是类的初始化操作比较耗时占资源。

上一篇下一篇

猜你喜欢

热点阅读