日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
雙重檢測(cè)就比餓漢式高級(jí)?那Kotlin的object為什么用餓漢式?

一、序

大家好,這里是承香墨影。

單例模式我相信大家應(yīng)該不會(huì)陌生,隨手抓一個(gè)程序員,讓他說(shuō)說(shuō)最常用的 3 種設(shè)計(jì)模式,其中一定包含單例模式。

單例最重要的是,關(guān)注唯一性以及線程安全問(wèn)題。而在 Java 中,單例存在多種實(shí)現(xiàn)范式,例如:餓漢式、懶漢式、靜態(tài)內(nèi)部類、雙重檢測(cè)等等,甚至還可以利用枚舉的特性實(shí)現(xiàn)單例,可謂是把單例玩出了花樣。

這其中,餓漢式單例實(shí)現(xiàn)代碼是最簡(jiǎn)單的,關(guān)鍵代碼只需一行 static final 申明對(duì)象即可,代碼簡(jiǎn)單且滿足需求。

但是餓漢式經(jīng)常會(huì)被我們"嫌棄",日常 Review Code 時(shí),甚至看到餓漢式單例也會(huì)「友善的建議」對(duì)方使用雙重檢測(cè)。

餓漢式依賴 JVM 加載類的時(shí)機(jī),來(lái)完成靜態(tài)對(duì)象的初始化,這個(gè)過(guò)程本身就是線程安全的。而它最被人詬病的,其實(shí)是無(wú)法延遲加載,完全依賴 JVM 加載類的時(shí)機(jī),這就導(dǎo)致單例類加載時(shí)機(jī)不可控。也就有可能,有些資源,業(yè)務(wù)還未使用,單例類就已經(jīng)準(zhǔn)備好了,導(dǎo)致過(guò)多的占用了系統(tǒng)資源。

我們?cè)倩剡^(guò)頭來(lái)看看 Kotlin。在 Kotlin 中,實(shí)現(xiàn)單例非常簡(jiǎn)單,只需要將關(guān)鍵字 class 替換為 object 即可。

 
 
 
 
  1. object SomeSingleton{
  2.   fun sayHi(){}
  3. }

但 Kotlin 的 object 其實(shí)就是餓漢式單例。它難道不怕存在資源占用的問(wèn)題嗎?

二、Kotliin 的 object

2.1 Kotlin 的 object 原理

在開(kāi)始 Kotlin 的 object 選擇餓漢式單例前,我們先來(lái)看看 Kotlin object 原理。

Kotlin 和 Java 可以互相調(diào)用,Kotlin 代碼運(yùn)行前也會(huì)被編譯器編譯成 Java 字節(jié)碼。那我們就可以通過(guò)工具將其還原為 Java 代碼進(jìn)行分析。

這個(gè)轉(zhuǎn)換工具, AS 原生支持。借助 AS 的 Tools → Kotlin → Show Kotlin Bytecode,就可以查看 Kotlin 編譯后的 Java 字節(jié)碼,再點(diǎn)擊 Decompile 按鈕,就可以將字節(jié)碼轉(zhuǎn)成 Java 代碼。

可以看到,INSTANCE 使用 static final 聲明,并且在 static 代碼塊內(nèi)對(duì)其進(jìn)行初始化,標(biāo)準(zhǔn)的餓漢式單例。

2.2 餓漢式如何保證唯一和線程安全?

前面提到,單例最重要的就是關(guān)注其唯一性和線程問(wèn)題。

需要在任何情況下,都確保一個(gè)類只存在一個(gè)實(shí)例,不會(huì)因?yàn)槎嗑€程的訪問(wèn),導(dǎo)致創(chuàng)建多個(gè)實(shí)例。同時(shí)也不會(huì)因?yàn)槎嗑€程而引入新的效率問(wèn)題。

餓漢式單例的原理,其實(shí)是基于 JVM 的類加載機(jī)制來(lái)保證其符合單例的規(guī)范的。

簡(jiǎn)單來(lái)說(shuō),JVM 在加載類的時(shí)候,會(huì)經(jīng)過(guò)初始化階段(即 Class 被加載后,且被線程使用前)。在初始化期間,JVM 會(huì)獲取一把鎖,這個(gè)鎖可以同步多個(gè)線程,對(duì)一個(gè)類的初始化,確保只有一個(gè)線程完成類的加載過(guò)程。這個(gè)步驟是線程安全的。

上圖很清晰的描述了類的初始化鎖工作流程,這里就不展開(kāi)細(xì)說(shuō)。

三、所謂的餓漢式問(wèn)題

前文提到,餓漢式單例最被人詬病的問(wèn)題,在于無(wú)法實(shí)現(xiàn)懶加載,完全依賴虛擬機(jī)加載類的策略加載。

3.1 懶加載

懶加載的目的,說(shuō)白了就是為了避免,無(wú)必要的資源浪費(fèi),在不需要的時(shí)候不加載,等什么時(shí)候業(yè)務(wù)真的需要使用到它的時(shí)候,再加載資源。

雖然餓漢式依賴虛擬機(jī)加載類的策略,但虛擬機(jī)本身也會(huì)有優(yōu)化項(xiàng),那就是「按需加載」的策略。

虛擬機(jī)在運(yùn)行程序時(shí),并不時(shí)在啟動(dòng)時(shí),就將所有的類都加載并初始化完成,而是采用「按需加載」的策略,在真正使用時(shí),才會(huì)進(jìn)行初始化。

例如 顯式的 new Class()、調(diào)用類的靜態(tài)方法、反射、Class.forName() 等,這些事件首次發(fā)生時(shí),都會(huì)觸發(fā)虛擬機(jī)加載類。

例如前文中,SomeSingleton 這個(gè)單例類,我們放到一個(gè) App 中運(yùn)行一下,App 先啟動(dòng),點(diǎn)擊按鈕執(zhí)行 SomeSingleton.sayHi() 方法。

 
 
 
 
  1. 15:39:34.539 I/cxmyDev: App running
  2. 15:39:44.606 I/cxmyDev: SomeSingleton init
  3. 15:39:44.606 I/cxmyDev: SomeSingleton sayHi

注意 Log 的時(shí)間,只有點(diǎn)擊按鈕執(zhí)行 SomeSingleton.sayHi() 時(shí),該單例類才被虛擬機(jī)加載。

也就是說(shuō),通常只有在你真實(shí)使用這個(gè)類時(shí),它才會(huì)真的被虛擬機(jī)初始化,我們并不需要擔(dān)心會(huì)被提前加載而導(dǎo)致資源浪費(fèi)。

當(dāng)然,不同虛擬機(jī)的實(shí)現(xiàn)方式不同,這并不是強(qiáng)制的,但是大多數(shù)為了性能都會(huì)準(zhǔn)守此規(guī)則。

3.2 軟件設(shè)計(jì)的角度

既然餓漢式的單例,也是在首次使用時(shí)初始化,這自然就是一種類懶加載的效果。

那我們?cè)贀Q個(gè)角度思考,如果餓漢式單例就是在程序啟動(dòng)時(shí),就初始化好了,有問(wèn)題嗎?

在 Java 中,其實(shí)構(gòu)造一個(gè)普通對(duì)象的成本很低。那為什么到了單例模式下,就覺(jué)得是個(gè)問(wèn)題呢?

主要是單例的生命周期較長(zhǎng),承載了業(yè)務(wù)和狀態(tài),我們不提前構(gòu)造無(wú)非是 2 個(gè)問(wèn)題。

  1. 單例對(duì)象本身,初始化比較復(fù)雜或耗時(shí),提前初始化會(huì)影響其他業(yè)務(wù);
  2. 單例初始化后,持有的資源太多,導(dǎo)致內(nèi)存資源的浪費(fèi);

問(wèn)題一:初始化邏輯復(fù)雜

如果單例在初始化階段,存在大量的邏輯,那么也不應(yīng)該等到需要使用時(shí)才初始化它,否者必然會(huì)影響到接下來(lái)的業(yè)務(wù)性能。而是應(yīng)該在此之前,系統(tǒng)較為空閑時(shí)初始化。

例如 Android 下就可以借助 IdleHandler 在空閑時(shí)提前做一些初始化工作。

問(wèn)題二:持有資源太多

系統(tǒng)的各項(xiàng)資源,從來(lái)就沒(méi)有夠的時(shí)候。

任何時(shí)候緩存和性能都是要平衡的,單例作為一個(gè)生命周期較長(zhǎng)的類,更不應(yīng)該長(zhǎng)時(shí)間持有大量的資源。否者就算加載時(shí)不報(bào)錯(cuò),也必然會(huì)埋下 OOM 隱患,是之后內(nèi)存優(yōu)化時(shí),重點(diǎn)關(guān)注的對(duì)象。

在編寫代碼時(shí),就思考對(duì)內(nèi)存資源的合理利用,而不是等到內(nèi)存問(wèn)題嚴(yán)重時(shí),再集中進(jìn)行內(nèi)存優(yōu)化。合理使用弱引用優(yōu)化持有資源,也是一種不錯(cuò)的優(yōu)化手段。

另外如果初始化時(shí),就是必須會(huì)占用一些資源,那么基于 Fail-fast 原則,有問(wèn)題也應(yīng)該盡早的暴露出來(lái)。

畢竟 App 崩潰在開(kāi)發(fā)手里,這叫問(wèn)題,而崩潰在用戶手里,這就叫事故。

四、小結(jié)

時(shí)刻今天我們聊了 Java 的單例,以及 Kotlin object 單例的實(shí)現(xiàn)原理,最后我們?cè)傩〗Y(jié)一下。

Kotlin object 使用「餓漢式」單例,依賴 JVM 的類加載機(jī)制確保唯一和線程安全;

JVM 加載類采用「按需加載」策略,確保懶加載;

Kotlin 的 object 選擇餓漢式單例,在性能和實(shí)現(xiàn)上都不存在問(wèn)題,使用它無(wú)需顧慮。


分享名稱:雙重檢測(cè)就比餓漢式高級(jí)?那Kotlin的object為什么用餓漢式?
鏈接URL:http://www.5511xx.com/article/cojhgoi.html