Kotlin 委托函數(shù)之lazy

Kotlin 標準庫為幾種有用的委托提供了工廠方法。

  • 延遲屬性(lazy properties): 其值只在首次訪問時計算桶唐;
  • 可觀察屬性(observable properties): 監(jiān)聽器會收到有關此屬性變更的通知;
  • 把多個屬性儲存在一個映射(map)中综芥,而不是每個存在單獨的字段中力崇。

延遲屬性 Lazy

lazy() 是接受一個 lambda 并返回一個 Lazy <T> 實例的函數(shù),返回的實例可以作為實現(xiàn)延遲屬性的委托.

val lazyValue: String by lazy {
    println("set lazyValue")
    "lazyInit"
}

fun main() {
    println(lazyValue)
    println(lazyValue)
}

// set lazyValue
// lazyInit
// lazyInit

從示例中可以看出,

當?shù)谝淮问褂玫皆撝禃r,會調用lambda函數(shù),并且將函數(shù)返回值,賦給該變量. 后續(xù)則直接返回該值,而不再調用lambda函數(shù).

修飾延遲屬性只能使用val,說明延遲屬性只能修飾不變的變量,它沒有對應的set函數(shù),只有get函數(shù).

kotlin角度分析Lazy

點開lazy方法,跳轉到LazyJVM.kt文件.

public actual fun <T> lazy(lock: Any?, initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer, lock)

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
            // 已經(jīng)初始化的時候,直接返回 初始化后的值
            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 {
                    // 通過傳入的函數(shù),得到函數(shù)返回值
                    val typedValue = initializer!!()
                    // 將返回值賦值給_value
                    _value = typedValue
                    // 置空函數(shù),輔助GC
                    initializer = null
                    typedValue
                }
            }
        }
    ...
}

lazy最后調用的是SynchronizedLazyImpl類中的相關方法.

當訪問字段變量的時候,最終調用就是對應的get方法.

_value有值時,則直接返回, 當_value沒有值時,通過調用傳入的lambda函數(shù),得到lambda函數(shù)的返回值作為_value的值返回.

由此,實現(xiàn)懶加載的功能.

可以看出, 默認該方式是使用同步鎖進行的,所以它是線程安全的.

即該值只在一個線程中計算欣舵,并且所有線程會看到相同的值。

lazy的三種模式

public enum class LazyThreadSafetyMode {

    /**
     * Locks are used to ensure that only a single thread can initialize the [Lazy] instance.
     */
    SYNCHRONIZED,

    /**
     * Initializer function can be called several times on concurrent access to uninitialized [Lazy] instance value,
     * but only the first returned value will be used as the value of [Lazy] instance.
     */
    PUBLICATION,

    /**
     * No locks are used to synchronize an access to the [Lazy] instance value; if the instance is accessed from multiple threads, its behavior is undefined.
     *
     * This mode should not be used unless the [Lazy] instance is guaranteed never to be initialized from more than one thread.
     */
    NONE,
}

lazy函數(shù)主要有3中模式

  • SYNCHRONIZED 同步方式

延遲屬性的值,只允許一個線程中修改,其他線程只能使用該線程修改的值.

  • PUBLICATION 公共方式
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) {
                // 初始化的lambda函數(shù)可被多個線程執(zhí)行
                val newValue = initializerValue()
                // 通過CAS操作,只有一個線程,能夠修改_value值
                if (valueUpdater.compareAndSet(this, UNINITIALIZED_VALUE, newValue)) {
                    initializer = null
                    return newValue
                }
            }
            @Suppress("UNCHECKED_CAST")
            return _value as T
        }

允許多個線程在同一時刻進入初始化階段,但是只有一個線程可以修改成功.

  • NONE 模式
get() {
            if (_value === UNINITIALIZED_VALUE) {
                _value = initializer!!()
                initializer = null
            }
            @Suppress("UNCHECKED_CAST")
            return _value as T
        }

線程不安全.

模式使用

val lazyValue: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
    println("set lazyValue")
    "lazyInit"
}

引用

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末缀磕,一起剝皮案震驚了整個濱河市缘圈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌袜蚕,老刑警劉巖糟把,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異牲剃,居然都是意外死亡遣疯,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門凿傅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缠犀,“玉大人,你說我怎么就攤上這事聪舒”嬉海” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵箱残,是天一觀的道長滔迈。 經(jīng)常有香客問我,道長被辑,這世上最難降的妖魔是什么燎悍? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮敷待,結果婚禮上间涵,老公的妹妹穿的比我還像新娘仁热。我一直安慰自己榜揖,他們只是感情好勾哩,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著举哟,像睡著了一般思劳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上妨猩,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天潜叛,我揣著相機與錄音,去河邊找鬼壶硅。 笑死威兜,一個胖子當著我的面吹牛,可吹牛的內容都是我干的庐椒。 我是一名探鬼主播椒舵,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼约谈!你這毒婦竟也來了笔宿?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤棱诱,失蹤者是張志新(化名)和其女友劉穎泼橘,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體迈勋,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡炬灭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了靡菇。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片担败。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖镰官,靈堂內的尸體忽然破棺而出提前,到底是詐尸還是另有隱情,我是刑警寧澤泳唠,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布狈网,位于F島的核電站,受9級特大地震影響笨腥,放射性物質發(fā)生泄漏拓哺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一脖母、第九天 我趴在偏房一處隱蔽的房頂上張望士鸥。 院中可真熱鬧,春花似錦谆级、人聲如沸烤礁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脚仔。三九已至勤众,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鲤脏,已是汗流浹背们颜。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留猎醇,地道東北人窥突。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像硫嘶,于是被迫代替她去往敵國和親波岛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內容