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的适合场景是类的初始化操作比较耗时占资源。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容