Singleton Pattern
單例模式是一種創建型設計模式,可以確保一個類別只有一個實例,並且提供一個全域接觸點。
單例模式結構

單例模式的應用場景
- 如果程式碼的某個類對於所有客戶端只有一個可用的實例
單例模式禁止偷過特殊建構方法以外的任何方式來創建自身類的實例
- 如果你需要加嚴格地控制全域變數
單例他能保證類只存在一個實例
優缺點
⭕優點
- 保證一個類只有一個實例
- 獲得一個指向實例地全域接觸點
- 僅在首次請求單例對象的時候才進行初始化
- 漸少開銷,因為只有一個實例,減少記憶體開銷,又不需要頻繁實例化,減少性能開銷
- 避免某個資源多重占用,例如文件執行寫操作時,單例可以保證對文件只有一個寫操作
❌缺點
- 違反單一職責原則。同時解決兩個問題
- 可能掩蓋不良設計,比如程式碼各組件之間相互了解過多等
- 在多執行緒環境下需要進行特殊處理,避免多執行緒多次創建單例對象
- 單例的客戶端程式碼單元測試可能比較困難,因為需多測試框架以基於繼承的方式創建對象。但單例類的建構式是私有的,而且絕大部分語言無法重寫靜態方法,需要想出模擬單例的方法。
單例模式(Singleton Pattern)
為什麼要用單例模式?
- 只需要一個實例地例子
- 執行緒池
- 快取
- 對話方塊
- 處理偏好設定
- 註冊設定的物件
- 做紀錄(logging)的物件
- 顯示卡驅動
- 當實例不只一個的時候,會遇到哪些問題?
- 不正確的程式行為
- 過度使用資源
- 產生錯誤的結果
- 與全域變數有什麼差別
- 全域變數會在程式開始執行期的時候建立,如果物件占用很多資源,卻從不使用很浪費。單例只在真正需要的時候才建立物件
- 使用全域變數可能會造成 namespace 的混亂
- 使用場景
- 某個類的實例需要被頻繁的創建,試用後需要頻繁銷毀
- 某個類的實例化耗時、占用資源多且使用頻繁
- 某個類的實例頻繁的與數據庫或文件交互
- 某 個類包含全域狀態,或在業務邏輯上僅應該有單一實例
經典的單例模式(執行緒不安全)
// NOTE: This is not thread safe!
public class Singleton {
private static Singleton uniqueInstance; // 我們用一個static變數來保存Singleton
private Singleton() {} // 建構式宣告成private,只有Singleton可以實例化這個類別
public static Singleton getInstance() { // 提供一個管道來讓你實例化這個類別
if (uniqueInstance == null) { // 還沒有建立過這個實例
uniqueInstance = new Singleton(); // 運用私有的建構式將他實例化,如果用不到就不會被建立,這是一種惰性(lazy)實例化
}
return uniqueInstance; // 回傳他的實例
}
// other useful methods here
public String getDescription() {
return "I'm a classic Singleton!";
}
}
提示
單例模式可以確保一個類別只有一個實例,並且提供一個全域接觸點
遇到的問題
警告
多執行緒中,可能一次創建了兩個實例出來

使用 synchronized 來處理多執行緒的問題
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
public static synchronized Singleton getInstance() { // 增加synchronized關鍵字,讓每一個執行緒都必須等到輪到他時,才可以進入這個方法
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
// other useful methods here
public String getDescription() {
return "I'm a thread safe Singleton!";
}
}
警告
同步的代價很高,而且同步只有第一次在執行這個方法時派上用場,所以當你將 uniqueInstance 設為單例之後,同步化就不需要了。
多執行緒可能的方法
- 如果 getInstance()的性能對你的應用程式來說沒那麼重要,那就放著不管
- 使用急性(eager)建立實例,而不是惰性(lazy)建立。
public class Singleton {
private static Singleton uniqueInstance = new Singleton(); // 用靜態初始設定式來建立單例實例,這是執行緒安全
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance; // 因為已經建立了,直接回傳實例
}
// other useful methods here
public String getDescription() {
return "I'm a statically initialized Singleton!";
}
}
- 靜態內部類。外部類被加載時,靜態內部類不會跟著一起被加載,而是在靜態內部類被使用的時候才會被加載並實例化,所以靜態內部類具有 Lazy 加載的優點,同時保證線程安全
public class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return SingletonInstance.uniqueInstance; // 只有呼叫的時候才會加載靜態內部類
}
private static class SingletonInstance { // 靜態內部類
private static Singleton uniqueInstance = new Singleton();
}
}
雙重檢查鎖版本(DCL)(多執行緒)
使用雙重檢查鎖,在 getInstance()中減少同步化
- volatile 保證可見性,避免 JVM 指令重排引發的問題
public class Singleton {
private volatile static Singleton uniqueInstance; // volatile保證可見性,避免JVM指令重排引發的問題
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) { // 只有在第一次時同步,並鎖上Class
if (uniqueInstance == null) { // 進入這個區塊後,還要再次檢查,如果他還是null,就建立一個實例
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}