單例模式
-
定義:
-
單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。
-
這種模式涉及到一個單一的類,該類負責建立自己的物件,同時確保只有單個物件被建立。這個類提供了一種訪問其唯一的物件的方式,可以直接訪問,不需要例項化該類的物件
-
我的理解:其實就是構造方法私有化,然後在本類中建立一個返回值為本類名的方法,用來使外部類呼叫此方法建立唯一的一個物件
-
-
特點:
-
單例類只能有一個例項。
-
單例類必須自己建立自己的唯一例項。
-
單例類必須給所有其他物件提供這一例項。
-
-
型別:
-
單例設計模式分類兩種
-
1.餓漢單例(類載入就會導致該單例項物件被建立)
-
優缺點:
-
優點:沒有加鎖,執行效率會提高。
-
缺點:類載入時就初始化,浪費記憶體。
-
-
例項
-
靜態變數方式的單例模式
-
該方式在成員位置宣告Singleton型別的靜態變數,並建立Singleton類的物件instance。instance物件是隨著類的載入而建立的。如果該物件足夠大的話,而一直沒有使用就會造成記憶體的浪費。
-
-
靜態程式碼塊方式的單例模式
-
該方式在成員位置宣告Singleton型別的靜態變數,而物件的建立是在靜態程式碼塊中,也是對著類的載入而建立。所以和餓漢式的方式1基本上一樣,當然該方式也存在記憶體浪費問題。
-
-
-
-
2.懶漢單例(類載入不會導致該單例項物件被建立,而是首次使用該物件時才會建立)
-
懶漢式-方式一:執行緒不安全(不推薦使用)
-
例項程式碼:
-
缺點:這種方式是最基本的實現方式,這種實現最大的問題就是不支援多執行緒。因為沒有加鎖 synchronized,所以嚴格意義上它並不算單例模式。
-
-
懶漢式-方式二:執行緒安全(synchronized)
-
例項程式碼:
-
優缺點:
-
優點:第一次呼叫才初始化,避免記憶體浪費。
-
缺點:必須加鎖 synchronized 才能保證單例,但加鎖會影響效率。
-
從下面程式碼我們可以看出,其實就是在初始化instance的時候才會出現執行緒安全問題,一旦初始化完成就不存在了。
-
-
-
懶漢式-方式三:雙重檢查鎖(推薦使用新增了volatile關鍵字的雙重檢查鎖)
-
例項程式碼:
-
再來討論一下懶漢模式中加鎖的問題,對於 getInstance() 方法來說,絕大部分的操作都是讀操作,讀操作是執行緒安全的,所以我們沒必讓每個執行緒必須持有鎖才能呼叫該方法,我們需要調整加鎖的時機。由此也產生了一種新的實現模式:雙重檢查鎖模式
-
有瑕疵的雙重檢查鎖程式碼(不推薦使用)
-
雙重檢查鎖模式是一種非常好的單例實現模式,解決了單例、效能、執行緒安全問題,上面的雙重檢測鎖模式看上去完美無缺,其實是存在問題,在多執行緒的情況下,可能會出現空指標問題,出現問題的原因是JVM在例項化物件的時候會進行優化和指令重排序操作。
-
解決方法:解決雙重檢查鎖模式帶來空指標異常的問題,只需要使用 volatile 關鍵字, volatile 關鍵字可以保證可見性和有序性。
-
完美的程式碼(強烈推薦)
-
新增 volatile 關鍵字之後的雙重檢查鎖模式是一種比較好的單例實現模式,能夠保證在多執行緒的情況下執行緒安全也不會有效能問題。
-
-
-
-
懶漢式-方式四:靜態內部類方式(強烈推薦使用)
-
靜態內部類單例模式中例項由內部類建立,由於 JVM 在載入外部類的過程中, 是不會載入靜態內部類的, 只有內部類的屬性/方法被呼叫時才會被載入, 並初始化其靜態屬性。靜態屬性由於被 static 修飾,保證只被例項化一次,並且嚴格保證例項化順序。
-
例項程式碼:
-
第一次載入Singleton類時不會去初始化INSTANCE,只有第一次呼叫getInstance,虛擬機器載入SingletonHolder並初始化INSTANCE,這樣不僅能確保執行緒安全,也能保證 Singleton 類的唯一性。
-
優點:靜態內部類單例模式是一種優秀的單例模式,是開源專案中比較常用的一種單例模式。在沒有加任何鎖的情況下,保證了多執行緒下的安全,並且沒有任何效能影響和空間的浪費。
-
-
-
-
-
列舉方式(惡漢式,比餓漢還吃記憶體)(不受序列化和反射的破壞)
-
列舉類實現單例模式是極力推薦的單例實現模式,因為列舉型別是執行緒安全的,並且只會裝載一次,設計者充分的使用了列舉的這個特性來實現單例模式,列舉的寫法非常簡單,而且列舉型別是所有單例實現中唯一一種不會被破壞的單例實現模式。
-
例項程式碼
-
列舉方式屬於惡漢式方式。
-
-
-
存在的問題
-
破壞單例模式:
-
使上面定義的單例類(Singleton)可以建立多個物件,列舉方式除外。有兩種方式破壞單例模式,分別是序列化和反射。
-
-
序列化破壞單例
-
測試類
-
被破壞的單例類
-
-
序列化破壞單例的解決方法
-
在Singleton類中新增readResolve()方法,在反序列化時被反射呼叫,如果定義了這個方法,就返回這個方法的值,如果沒有定義,則返回新new出來的物件。
-
單例類的改進
-
原始碼解析:
-
ObjectInputStream類
-
-
-
-
反射破壞單例
-
測試類
-
被破壞的單例類
-
-
-
反射破壞單例的解決方法
-
單例類
-
測試類
-
-
-
-
JDK原始碼存在的單例模式
-
例如Rumtime.java
-
典型的餓漢型(靜態變數)單例模式
-
-
例子(使用Runtime類中的方法)
-
-
以上內容整理於 幕布文件,相關視訊連結:https://www.bilibili.com/video/BV1Np4y1z7BU?p=22