文章目錄 1、模式定義 2、代碼實現 (1)雙重判空加鎖方式 兩次判空的作用? volatile 關鍵字的作用? 構造函數私有? (2)靜態內部類【推薦】 (3)Kotlin中的單例模式 3、優缺點 4、參考資料
1、模式定義
作為對象的創建模式,單例模式確保某一個類只有一個實例,而且自行實例化并向整個系統提供這個實例。這個類稱為單例類。 單例特別:(1)單例模式只能有一個實例。(2)單例類必須創建自己的唯一實例。(3)單例類必須向其他對象提供這一實例。
2、代碼實現
(1)雙重判空加鎖方式
public class Singlnton { private static volatile Singlnton instance; private Singlnton ( ) { } public static Singlnton getInstance ( ) { if ( instance== null ) { synchronized ( Singlnton . class ) { if ( instance== null ) { instance = new Singlnton ( ) ; } } } return instance; } }
兩次判空的作用?
volatile 關鍵字的作用?
雙重檢鎖單例模式在 CPU 的工作流,主要分為三步,1:分配內存對象空間。2:初始化對象。3:設置 instance 執行剛才分配的內存地址 。注意 JVM 和 CPU 優化會指令重排 ,上面順序會變成 1 -> 3 -> 2,單線程環境下,此順序是沒有問題,2 和 3 前后沒有依賴性,但是在多線程情況下會有這種情況,當線程 A 在執行第 5 行代碼時,B 線程進來執行到第 2 行代碼。假設此時 A 執行的過程中發生了指令重排序,即先執行了 1 和 3,沒有執行 2。那么由于 A 線程執行了 3 導致 instance 指向了一段地址,所以 B 線程判斷 instance 不為 null,會直接跳到第 6 行并返回一個未初始化的對象,此時會產生空指針異常 。volatile 能保持指令的有序性,能夠有效禁止指令重排序。 注意:“Singlnton instance” 相當于分配內存對象空間;“new Singlnton()” 相當于初始化對象;"="相當于設置 instance 執行剛才分配的內存地址。
構造函數私有?
無法通過調用該類的構造函數來實例化該類的對象,只有通過該類提供的靜態方法 getInstance 來得到該類的唯一實例
(2)靜態內部類【推薦】
public class Singlnton { private Singlnton ( ) { } private static class SinglntonHolder { private static Singlnton INSTANCE = new Singlnton ( ) ; } public static Singlnton getInstance ( ) { return SinglntonHolder . INSTANCE ; } }
利用 JAVA 虛擬機加載類的特性實現延遲加載和線程安全 由于靜態單例對象沒有作為 Singleton 的成員變量直接實例化,因此類加載時不會實例化 Singleton,第一次調用 getInstance() 時將加載內部類 SingletonHolder,在該內部類中定義了一個 static 類型的變量 INSTANCE,此時會首先初始化這個成員變量,由 Java 虛擬機來保證其線程安全性,確保該成員變量只能初始化一次。由于 getInstance() 方法沒有任何線程鎖定,因此其性能不會造成任何影響。 類加載機制
(3)Kotlin中的單例模式
class Singlnton private constructor ( ) { companion object { val instance : Singlnton by lazy ( mode = LazyThreadSafetyMode. SYNCHRONIZED) { Singlnton ( ) } } } class Singlnton private constructor ( ) { companion object { @Volatile private var instance: Singlnton? = null fun getInstance ( ) : Singlnton = instance ?: synchronized ( this ) { instance ?: Singlnton ( ) . also { instance = it} } } }
lateinit 和 by lazy 的區別:
lateinit 只能用于修飾變量 var,不能用于可空的屬性和 Java 的基本類型。 lateinit 可以在任何位置初始化并且可以初始化多次。 lazy() 只能用于修飾常量 val,并且 lazy() 是線程安全的。 lazy() 是一個函數,可以接受一個 Lambda 表達式作為參數,第一次調用時會執行 Lambda 表達式,以后調用該屬性會返回之前的結果。 lazy() 源碼分析
3、優缺點
優點: (1)在內存里只有一個實例,減少了內存的開銷,尤其是頻繁的創建和銷毀實例。 (2)避免對資源的多重占用。 缺點: (1)沒有接口,不能繼承,與單一職責原則沖突,一個類應該只關心內部邏輯,而不關心外面怎么樣來實例化。 (2)濫用單例會帶來一些負面問題。如果實例化的對象長時間不被使用,系統會認定為垃圾而回收,這將導致共享對象狀態的改變 注意事項:++不能使用反射調用私有構造器,這樣會實例化一個新的對象 ++ 如何避免反射創建新的單例對象
4、參考資料
單例模式 Android 校招面試指南(只能在手機端打開)