定義
單例模式(Singleton Pattern)是一種建立型設計模式,它確保一個類只有一個例項,並提供一個全域性訪問點來訪問該例項。這種模式的核心在於控制類的例項化過程,保證在任何時間點,一個類只有一個例項存在,並且這個例項可以被系統的所有其他部分透過一個公共的訪問點訪問。
1、唯一例項:單例模式要求一個類在任何情況下只能建立一個例項。這意味著無論多少次嘗試建立物件,或者透過什麼方式建立物件,最終都應當返回同一個例項。
2、全域性訪問點:單例模式提供了一個統一的介面或方法,允許程式的任何其他部分無需關心例項的建立細節,就能夠訪問到這個唯一的例項。
3、自我例項化:單例類負責自己的例項化過程。它通常會提供一個靜態方法,如getInstance(),用於返回類的唯一例項。如果例項不存在,該方法會建立一個例項,並在後續的呼叫中返回這個例項。
4、私有建構函式:為了避免外部透過new關鍵字或其他方式建立新的例項,單例類的建構函式通常被宣告為private,這樣就只能由類本身來例項化。
5、執行緒安全:在多執行緒環境中,單例模式還需要確保執行緒安全,即在多個執行緒同時訪問時,也能確保只建立一個例項。這通常透過同步機制(如 synchronized 關鍵字)或高階別的併發控制來實現。
6、序列化安全:單例模式還需要考慮序列化和反序列化的安全問題。為了確保反序列化不會建立新的例項,可以透過實現Serializable介面並定義readResolve()方法來保證例項的唯一性。
實現方式
特點 | 優點 | 缺點 | |
---|---|---|---|
懶漢式(執行緒不安全) | 第一次呼叫getInstance()方法時才建立例項。 | 實現了延遲載入,節省資源。 | 在多執行緒環境下可能建立多個例項,需要額外的同步機制來保證執行緒安全。 |
懶漢式(執行緒安全) | 第一次呼叫getInstance()方法時才建立例項,並使用synchronized關鍵字來保證執行緒安全。 | 在多執行緒環境下也能確保只建立一個例項。 | 每次呼叫getInstance()都需要進行同步,效率較低。 |
餓漢式 | 類載入時就完成例項化,避免了執行緒同步問題 | 實現簡單,無需考慮執行緒同步問題 | 不管是否使用,都會佔用資源,可能導致記憶體浪費 |
雙重檢查鎖定 | 兩次檢查例項是否已建立,如果未建立則進行同步建立。 | 結合了懶漢式和餓漢式的優點,既節省資源又保證了執行緒安全 | 實現相對複雜,需要使用volatile關鍵字來防止指令重排 |
靜態內部類 | 使用靜態內部類來實現單例,利用類載入的機制保證執行緒安全 | 實現了延遲載入和執行緒安全,且實現簡單 | 不能透過反射破壞單例模式,但需要了解類載入機制 |
列舉 | 使用列舉型別來實現單例,JDK內部保證每個列舉值只有一個例項 | 簡單、執行緒安全,防止反序列化攻擊 | 只能用於單例模式的實現,不能用於其他場景 |
註冊式(使用登記表) | 使用一個全域性的登錄檔來記錄和管理單例例項 | 可以在執行時動態建立和管理單例例項 | 增加了系統的複雜性,需要額外的管理和維護 |
使用容器 | 利用依賴注入框架的單例作用域來管理單例例項 | 程式碼簡潔,易於管理和測試 | 依賴於特定的框架,減少了程式碼的可移植性 |
程式碼示例
餓漢式
透過將建構函式設定為私有,確保外部無法直接透過new關鍵字建立例項。類內部建立一個該類的靜態例項,並透過一個公共的靜態方法返回這個例項。
package com.example.helloworld;
public class HungryMan {
//私有靜態例項
private static final HungryMan instance = new HungryMan();
//私有建構函式,防止外部例項化
private HungryMan(){}
//公共靜態方法,返回唯一例項
public static HungryMan getInstance(){
System.out.println("這是一個餓漢式單例");
return instance;
}
public static void main(String args[]){
HungryMan.getInstance();
}
}
這是一個餓漢式單例
懶漢式
透過將建構函式設定為私有,確保外部無法直接透過new關鍵字建立例項。類內部通常使用一個靜態變數來儲存例項,並設定為null初始值。透過一個公共的靜態方法來獲取例項,如果例項為null,則建立一個新例項,並將其賦值給靜態變數;如果例項已經存在,則直接返回該例項。
package com.example.helloworld;
public class LazyMan {
//私有靜態例項並設定初始值為null
private static LazyMan instance = null;
//私有函式,防止外部例項化
private LazyMan(){}
//公共靜態方法,返回唯一例項
public static synchronized LazyMan getInstance(){
if (instance == null){
instance = new LazyMan();
System.out.println("建立了一個新例項");
}
else {System.out.println("例項已存在,直接返回");}
return instance;
}
public static void main(String args[]){
LazyMan.getInstance();
}
}
建立了一個新例項