Java設計模式【單例模式】

單例模式
單例模式(Singleton Pattern)是一種創建型設計模式,其主要目的是確保一個類只有一個實體,并提供對該實體的唯一訪問點,
優缺點
優點:
-
提供了對唯一實體的受控訪問,
-
由于在系統記憶體中只存在一個物件,因此可以節約系統資源,
缺點:
-
單例類的擴展有很大的困難,
-
單例類的職責過重,在一定程度上違背了“單一職責原則”,
-
物件生命周期, 單例模式沒有提出物件的銷毀,在提供記憶體的管理的開發語言中,只有單例模式物件自己才能將物件實體銷毀,因為只有它擁有對實體的參考, 在各種開發語言中,比如C++,其他類可以銷毀物件實體,但是這么做將導致單例類內部的指標指向不明,
單例模式的使用
餓漢模式
- 靜態成員變數
/**
* @author Physicx
* @date 2023/5/12 下午10:13
* @desc 單例
* Created with IntelliJ IDEA
*/
public class Singleton {
//初始化實體物件
private static final Singleton instance = new Singleton();
//私有化構造方法
private Singleton() {
}
//提供獲取實體物件方法
public static Singleton getInstance() {
return instance;
}
}
- 靜態代碼塊
/**
* @author Physicx
* @date 2023/5/12 下午10:13
* @desc 單例
* Created with IntelliJ IDEA
*/
public class Singleton {
//實體物件
private static final Singleton instance;
static {
instance = new Singleton();
}
//私有化構造方法
private Singleton() {
}
//提供獲取實體物件方法
public static Singleton getInstance() {
return instance;
}
}
餓漢式單例的寫法適用于單例物件較少的情況,這樣寫可以保證絕對的執行緒安全,執行效率比較高,但是缺點也很明顯,餓漢式會在類加載的時候就將所有單例物件實體化,這樣系統中如果有大量的餓漢式單例物件的存在,系統初始化的時候會造成大量的記憶體浪費,換句話說就是不管物件用不用,物件都已存在,占用記憶體,
懶漢模式
public class Singleton {
//實體物件
private static Singleton instance;
//私有化構造方法
private Singleton() {
}
//提供獲取實體物件方法(執行緒安全)
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
執行緒安全的一種懶漢式寫法,在類第一次使用的時候初始化,獲取實體的靜態方法由synchronized修飾,所以是執行緒安全的,這種方法每次獲取實體物件都加鎖同步,效率較低,
雙重檢測機制(DCL)
public class Singleton {
//實體物件
private static volatile Singleton instance;
//私有化構造方法
private Singleton() {
}
//提供獲取實體物件方法
public static Singleton getInstance() {
if (instance == null) {
//加鎖處理
synchronized (Singleton.class) {
if (instance==null) {
//初始化
instance = new Singleton();
}
}
}
return instance;
}
}
實體物件必須用 volatile 修飾,否則極端情況可能出現安全隱患,
以上初始化物件代碼被編譯后會變成以下三條指令:
-
分配物件的記憶體空間,
-
初始化物件,
-
設定instance指向剛才分配的記憶體空間,
如果按照上面的執行順序則不加volatile沒有問題,但是CPU或編譯器為了提高效率,可能會進行指令重排,最終順序變為:
-
分配物件的記憶體空間,
-
設定instance指向剛才分配的記憶體空間,
-
初始化物件,
當兩個執行緒同時獲取實體物件時,執行緒A已經將instance指向分配空間但未初始化物件,執行緒B此時第一次判空已不為空,于是回傳instance實體,但是此時回傳的實體未初始化會導致后續空指標例外,
DCL這種方式同樣也是類第一次使用的時候初始化,初始化代碼synchronized修飾執行緒安全,這種方式只會第一次實體物件才會進行同步,因此效率高,
《Java Concurrency in Practice》作者Brian Goetz在書中提到關于DCL的觀點:促使DCL模式出現的驅動力(無競爭同步的執行速度很慢,以及JVM啟動時很慢)已經不復存在,因而它不是一種高效的優化措施,延遲初始化占位類模式(靜態內部類)能帶來同樣的優勢,并且更容易理解,
靜態內部類(延遲初始化)
public class Singleton {
//私有化構造方法
private Singleton(){}
//靜態內部類(被呼叫時加載)
private static class SingletonHandle {
private static final Singleton instance = new Singleton();
}
//提供獲取實體物件方法
public static Singleton getInstance() {
return SingletonHandle.instance;
}
}
利用靜態內部類被呼叫時才加載的特性,通過靜態初始化初始Singleton物件,由于JVM將在初始化期間獲得一個鎖,并且每個執行緒都至少獲取一次這個鎖以確保這個類已經加載,因此在靜態初始化期間,記憶體寫入操作將自動對所有執行緒可見,因此無論是在被構造期間還是被參考時,靜態初始化的物件都不需要顯式的同步,
執行緒安全,效率高,使用的時候才會初始化不浪費記憶體,
《Java Concurrency in Practice》作者Brian Goetz 推薦這種單例實作方式,
列舉實作方式
除了以上幾種常見的實作方式之外,Google 首席 Java 架構師、《Effective Java》一書作者、Java集合框架的開創者Joshua Bloch在Effective Java一書中提到:單元素的列舉型別已經成為實作Singleton的最佳方法,
在這種實作方式中,既可以避免多執行緒同步問題;還可以防止通過反射和反序列化來重新創建新的物件,
public class Singleton {
//私有化構造方法
private Singleton() {}
enum SingletonEnum {
SINGLETON;
private final Singleton instance;
SingletonEnum() {
instance = new Singleton();
}
//提供獲取實體物件方法
public Singleton getInstance() {
return instance;
}
}
}
呼叫方式如下:
public static void main(String[] args) {
Singleton instance1 = Singleton.SingletonEnum.SINGLETON.getInstance();
Singleton instance2 = Singleton.SingletonEnum.SINGLETON.getInstance();
System.out.println(instance2 == instance1);
}
普通的單例模式是可以通過反射和序列化/反序列化來破解的,jvm虛擬機會保證列舉型別不能被反射并且建構式只被執行一次,而Enum由于自身的特性問題,是無法破解的,當然,由于這種情況基本不會出現,因此我們在使用單例模式的時候也比較少考慮這個問題,
總結
| 實作方式 | 優點 | 缺點 |
|---|---|---|
| 餓漢模式 | 執行緒安全,效率高 | 非懶加載 |
| 懶漢模式 | 執行緒安全,懶加載 | 效率低 |
| 雙重檢測機制 | 執行緒安全,懶加載,效率高 | |
| 靜態內部類 | 執行緒安全,懶加載,效率高 | |
| 列舉 | 執行緒安全,效率高 | 非懶加載 |
由于單例模式的列舉實作代碼比較簡單,而且又可以利用列舉的特性來解決執行緒安全和單一實體的問題,還可以防止反射和反序列化對單例的破壞,因此在很多書和文章中都強烈推薦將該方法作為單例模式的最佳實作方法,
參考:單例模式詳解(知乎文章)
設計模式相關其他文章:
Java設計模式總結
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/552356.html
標籤:設計模式
上一篇:Java設計模式簡介(總結)
下一篇:返回列表
