單例模式(Singleton Pattern)
在我們的系統中,有一些物件其實我們只需要一個,比如說:執行緒池、快取、對話方塊、登錄檔、日誌物件、充當印表機、顯示卡等裝置驅動程式的物件。事實上,這一類物件只能有一個例項,如果製造出多個例項就可能會導致一些問題的產生,比如:程式的行為異常、資源使用過量、或者不一致性的結果。
如何保證一個類只有一個例項並且這個例項易於被訪問呢?定義一個全域性變數可以確保物件隨時都可以被訪問,但不能防止我們例項化多個物件。
更好的解決辦法是讓類自身負責儲存它的唯一例項。這個類可以保證沒有其他例項被建立,並且它可以提供一個訪問該例項的方法。
定義
單例模式(Singleton Pattern):單例模式確保某一個類只有一個例項,而且自行例項化並向整個系統提供這個例項,這個類稱為單例類,它提供全域性訪問的方法。
J2EE 標準中的 ServletContext 和 ServletContextConfig、Spring框架應用中的 ApplicationContext、資料庫中的連線池等也都是單例模式。
單例模式的要點有三個:
- 單例類只能有一個例項物件;
- 該單例物件必須由單例類自行建立;
- 單例類對外提供一個訪問該單例的全域性訪問點。
結構
單例模式的主要角色如下。
- 單例類:包含一個例項且能自行建立這個例項的類。
- 訪問類:使用單例的類。
以懶漢式為例,UML類圖如下:
時序圖如下:
實現
Ⅰ餓漢式-靜態常量
public class Singleton {
// 構造器私有化,使用者無法通過new方法建立該物件例項
private Singleton() {
}
// 在靜態初始化器中建立單例例項,這段程式碼保證了執行緒安全
private static Singleton uniqueInstance = new Singleton();
// 提供一個公有的靜態方法,返回例項物件
public static Singleton getInstance() {
return uniqueInstance;
}
}
優點:這種寫法比較簡單,在類裝載的時候就完成例項化,避免了執行緒同步等問題,是執行緒安全的。
缺點:在類裝載的時候就完成例項化,沒有達到懶載入(Lazy Loading)的效果。如果從始至終從未使用過這個例項,則會造成記憶體浪費。
這種單例模式可用,但可能造成記憶體浪費。
Ⅱ 餓漢式-靜態程式碼塊
public class Singleton {
private Singleton() {}
private static Singleton uniqueInstance;
// 靜態程式碼塊中建立例項
static {
uniqueInstance = new Singleton();
}
public static Singleton getInstance() {
return uniqueInstance;
}
}
與上面類似,也是在類裝載的時候就完成例項化,只不過將類例項化的過程放在了靜態程式碼塊中。
這種單例模式可用,但可能造成記憶體浪費。
Ⅲ 懶漢式-執行緒不安全
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
// 提供一個靜態的公有方法,當使用到該方法時,才去建立uniqueInstance
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
優點:起到了**懶載入(Lazy Loading)**的效果,如果沒有用到該類,那麼就不會例項化 uniqueInstance,從而節約資源。
缺點:執行緒不安全。如果多個執行緒能夠同時進入 if (uniqueInstance == null)
,並且此時 uniqueInstance 為 null,那麼會有多個執行緒執行 uniqueInstance = new Singleton();
語句,這將導致例項化多次 uniqueInstance
實際開發中,不要使用。
Ⅳ 懶漢式-執行緒安全
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
}
// 提供一個靜態的公有方法,當使用到該方法時,才去建立uniqueInstance
public static synchronized Singleton getUniqueInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
優點:執行緒安全。只需要對 getUniqueInstance() 方法加鎖,那麼在一個時間點只能有一個執行緒能夠進入該方法,從而避免了例項化多次 uniqueInstance。
缺點:效率低。每個執行緒在想獲得類的例項時候,執行 getInstance()方法都要先進行同步,即使 uniqueInstance 已經被例項化了,這會讓執行緒阻塞時間過長。
實際開發中,不推薦使用。
Ⅴ 懶漢式-執行緒安全-雙重校驗鎖
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
//提供一個靜態的公有方法,加入雙重檢查程式碼,解決執行緒安全問題, 同時解決懶載入問題
public static Singleton getUniqueInstance() {
if (uniqueInstance == null) {// 避免已經例項化後的加鎖操作
synchronized (Singleton.class) {
if (uniqueInstance == null) {// 避免多個執行緒同時進行例項化操作
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
**雙重校驗鎖(Double-Check Locking)**概念是多執行緒開發中常使用到的。假如在uniqueInstance == null
的情況下,兩個執行緒都執行了 if 語句,那麼兩個執行緒都會進入 if 語句塊內。可以肯定都是,兩個執行緒都會執行 uniqueInstance = new Singleton();
這條語句,只是先後的問題,也就是說肯定會有兩次例項化。
因此必須需要使用兩個 if 語句:第一個 if 語句用來避免 uniqueInstance 已經被例項化之後的加鎖操作;第二個 if 語句進行了加鎖,只允許一個執行緒進入,保證了執行緒安全,避免出現uniqueInstance == null
時兩個執行緒同時進行例項化操作問題。
uniqueInstance
採用 volatile關鍵字修飾也是很有必要的,因為 JVM了效能優化,可能會指令重排。指令重排在單執行緒環境下不會出現問題,但是在多執行緒環境下會導致一個執行緒獲得還沒有初始化的例項。而使用 volatile 修飾uniqueInstance後會引入記憶體屏障(Memory Barrier),保證了JVM的可見性與有序性。(雙重校驗鎖詳解可參考之前寫的【併發程式設計】volatile一文)
總之,雙重檢驗鎖保證了執行緒安全,同時懶漢式保證了延遲載入。
效率較高,推薦使用。
Ⅵ 靜態內部類
class Singleton {
//構造器私有化
private Singleton() {
}
// 靜態內部類,該類中有一個靜態屬性 Singleton
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
// 提供一個靜態的公有方法,直接返回 SingletonInstance.INSTANCE
public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
}
當 Singleton 類被載入時,靜態內部類 SingletonHolder 沒有被載入進記憶體。只有當呼叫 getUniqueInstance()
方法從而觸發 SingletonHolder.INSTANCE
時 SingletonHolder 才會被載入,此時初始化 INSTANCE 例項,並且 JVM 能確保 INSTANCE 只被例項化一次。
這種方式不僅具有延遲初始化的好處,利用靜態內部類特點實現了延遲載入,而且由 JVM 提供了對執行緒安全的支援。
效率高,推薦使用。
VII 列舉
enum Singleton {
INSTANCE; //屬性
public void doSomeTing() {
System.out.println("通過列舉方法實現單例");
}
}
使用:
public class EnumSingletonTest {
public static void main(String[] args) {
Singleton singleton = Singleton.INSTANCE;
singleton.doSomeThing();// 通過列舉方法實現單例
}
}
這種方式是Effective Java 作者Josh Bloch 提倡的方式。雖然這種方法還沒有廣泛採用,但是單元素的列舉型別已經成為實現Singleton的最佳方法。這種方法在功能上與公有域方法相近,但是它更加簡潔,無償提供了序列化機制,絕對防止多次例項化,即使是在面對複雜序列化或者反射攻擊的時候。詳細分析可參考Java單例模式的7種寫法中,為何用Enum列舉實現被認為是最好的方式一文。
推薦使用。
JDK
總結
優點
- 提供了對唯一例項的受控訪問。
- 由於在系統記憶體中只存在一個物件,因此可以節約系統資源,對於一些需要頻繁建立和銷燬的物件,單例模式無疑可以提高系統的效能。
- 允許可變數目的例項。我們可以基於單例模式進行擴充套件,使用與單例控制相似的方法來獲得指定個數的物件例項。
缺點
- 由於單例模式中沒有抽象層,因此單例類的擴充套件有很大的困難。
- 單例類的職責過重,在一定程度上違背了“單一職責原則”。因為單例類既充當了工廠角色,提供了工廠方法,同時又充當了產品角色,包含一些業務方法,將產品的建立和產品的本身的功能融合到一起。
- 濫用單例將帶來一些負面問題,如為了節省資源將資料庫連線池物件設計為單例類,可能會導致共享連線池物件的程式過多而出現連線池溢位;Java、執行環境中提供了自動垃圾回收的技術,如果例項化的物件長時間不被利用,系統會認為它是垃圾,會自動銷燬並回收資源,下次利用時又將重新例項化,這會導致物件狀態的丟失。
適用場景
需要頻繁的進行建立和銷燬、或者建立時耗時過多或耗費資源過多但又經常用到的物件;工具類物件;頻繁訪問資料庫或檔案的物件(比如資料來源、session工廠等)。
參考
相關文章
- 設計模式--單例(Singleton Pattern)設計模式單例
- 常用設計模式-單例模式(Singleton pattern)設計模式單例
- 設計模式之單例模式(Singleton Pattern)設計模式單例
- PHP設計模式(一)—單例模式(Singleton Pattern)PHP設計模式單例
- 深入理解 JavaScript 單例模式 (Singleton Pattern)JavaScript單例模式
- 單例模式(Singleton)單例模式
- 單例模式 singleton單例模式
- 設計模式—singleton(單例模式)設計模式單例
- laravel singleton 單例模式使用Laravel單例模式
- Singleton——單例模式(8種)單例模式
- Singleton 單例設計模式單例設計模式
- 設計模式系列之單例模式(Singleton Pattern)——確保物件的唯一性設計模式單例物件
- java設計模式-單例模式SingletonJava設計模式單例
- [DELPHI]單例模式(singleton) 陳省單例模式
- Singleton(單例)——物件建立型模式單例物件模式
- PHP設計模式(四)單例模式(Singleton)PHP設計模式單例
- 《設計模式》 - 1. 單例模式( Singleton )設計模式單例
- Scala 與設計模式(一):Singleton 單例模式設計模式單例
- python3中的單例模式SingletonPython單例模式
- 使用C# (.NET Core) 實現單體設計模式 (Singleton Pattern)C#設計模式
- 設計模式之“物件效能模式”: Singleton 單例模式(筆記)設計模式物件單例筆記
- 設計模式 - 單例模式Singleton的8種寫法設計模式單例
- Python單例模式(Singleton)的N種實現Python單例模式
- 2.C#設計模式系列01_單例模式_SingletonC#設計模式單例
- 一天一個設計模式(二) - 單例模式(Singleton)設計模式單例
- JAVA中實現單例(Singleton)模式的八種方式Java單例模式
- 設計模式:命令模式(Command Pattern)及例項設計模式
- 簡單工廠模式( Simple Factory Pattern )模式
- 單例模式單例模式
- Singleton設計模式設計模式
- Spring IoC 中的(Singleton)單例物件建立過程探索Spring單例物件
- 設計模式 單例模式設計模式單例
- 設計模式——單例模式設計模式單例
- [設計模式] 單例模式設計模式單例
- 設計模式-單例模式設計模式單例
- 創造模式 單例模式模式單例
- 建立型模式:單例模式模式單例
- 設計模式 —— 單例模式設計模式單例