單例模式(Singleton Pattern)詳解
一、單例模式簡介
單例模式(Singleton Pattern) 是一種 創建型設計模式,它確保一個類只有一個實例,并提供一個全局訪問點來獲取這個實例。(對象創建型模式)
簡單來說,就是:
“在整個應用程序中,某個類只能有一個對象存在。”
這在需要共享資源、統一管理狀態或控制訪問時非常有用。
要點:
某個類只能有一個實例
必須自行創建這個實例
必須自行向整個系統提供這個實例
單例模式只包含一個單例角色:
Singleton(單例)
私有構造函數
靜態私有成員變量(自身類型)
靜態公有的工廠方法
二、解決的問題類型
單例模式主要用于解決以下問題:
- 多個對象造成資源浪費:比如數據庫連接池、線程池等,如果每次使用都新建對象,會造成性能浪費。
- 需要全局唯一訪問入口:如配置管理器、日志記錄器等,希望整個系統中都通過同一個接口訪問。
- 避免重復初始化帶來的錯誤:例如緩存服務、任務調度器等,不允許多個實例同時運行。
三、使用場景
場景 | 示例 |
---|---|
配置中心 | 如 ConfigManager 管理應用的配置信息 |
日志記錄器 | 如 Logger 實現統一的日志輸出 |
數據庫連接池 | 如 DataSource 提供連接復用 |
緩存服務 | 如 CacheManager 統一管理緩存數據 |
線程池管理 | 如 ExecutorService 控制并發資源 |
四、實際生活案例
想象你在家里只有一臺電視遙控器。無論你是爸爸、媽媽還是孩子,大家都必須通過這一個遙控器來操作電視。如果你允許每個家庭成員都擁有自己的遙控器,那么可能會出現混亂操作和資源浪費。
在這個例子中,“遙控器”就是一個“單例”,全家人共享一個實例。
五、代碼案例(Java)
1. 懶漢式(線程不安全)
class SingletonLazy {private static SingletonLazy instance;private SingletonLazy() {}public static SingletonLazy getInstance() {if (instance == null) {instance = new SingletonLazy();}return instance;}
}
?? 注意:這種方式在多線程環境下可能創建多個實例。
2. 懶漢式 + 同步方法(線程安全)
class SingletonLazySync {private static SingletonLazySync instance;private SingletonLazySync() {}public static synchronized SingletonLazySync getInstance() {if (instance == null) {instance = new SingletonLazySync();}return instance;}
}
? 優點:線程安全
? 缺點:效率低,每次調用 getInstance()
都會加鎖
多個線程同時訪問將導致創建多個單例對象!怎么辦?👇👇👇
3. 雙重檢查鎖定(Double-Checked Locking)
class SingletonDCL {private static volatile SingletonDCL instance;private SingletonDCL() {}public static SingletonDCL getInstance() {if (instance == null) {synchronized (SingletonDCL.class) {if (instance == null) {instance = new SingletonDCL();}}}return instance;}
}
? 優點:線程安全、性能較好
? 推薦方式之一
4. 餓漢式(靜態常量)
class SingletonEager {private static final SingletonEager INSTANCE = new SingletonEager();private SingletonEager() {}public static SingletonEager getInstance() {return INSTANCE;}
}
? 優點:實現簡單、線程安全
? 缺點:類加載時就初始化,可能造成資源浪費
5. 靜態內部類(推薦寫法)
class SingletonInnerClass {private SingletonInnerClass() {}private static class SingletonHolder {private static final SingletonInnerClass INSTANCE = new SingletonInnerClass();}public static SingletonInnerClass getInstance() {return SingletonHolder.INSTANCE;}
}
? 優點:懶加載、線程安全、簡潔高效
? 最推薦寫法之一
餓漢式單例類與懶漢式單例類比較
餓漢式單例類:無須考慮多個線程同時訪問的問題;調用速度和反應時間優于懶漢式單例;資源利用效率不及懶漢式單例;系統加載時間可能會比較長
懶漢式單例類:實現了延遲加載;必須處理好多個線程同時訪問的問題;需通過雙重檢查鎖定等機制進行控制,將導致系統性能受到一定影響
6. 枚舉(最佳實踐)
enum SingletonEnum {INSTANCE;public void doSomething() {System.out.println("Doing something...");}
}
? 優點:
- 天然線程安全
- 天然防止反射攻擊
- 天然支持序列化/反序列化不破壞單例
? Joshua Bloch 推薦寫法(《Effective Java》作者)
其他案例:
-
某軟件公司承接了一個服務器負載均衡(Load Balance)軟件的開發工作,該軟件運行在一臺負載均衡服務器上,可以將并發訪問和數據流量分發到服務器集群中的多臺設備上進行并發處理,提高了系統的整體處理能力,縮短了響應時間。由于集群中的服務器需要動態刪減,且客戶端請求需要統一分發,因此需要確保負載均衡器的唯一性,只能有一個負載均衡器來負責服務器的管理和請求的分發,否則將會帶來服務器狀態的不一致以及請求分配沖突等問題。如何確保負載均衡器的唯一性是該軟件成功的關鍵,試使用單例模式設計服務器負載均衡器。
-
身份證號碼:在現實生活中,居民身份證號碼具有唯一性,同一個人不允許有多個身份證號碼,第一次申請身份證時將給居民分配一個身份證號碼,如果之后因為遺失等原因補辦時,還是使用原來的身份證號碼,不會產生新的號碼。現使用單例模式模擬該場景。
-
打印池:在操作系統中,打印池(Print Spooler)是一個用于管理打印任務的應用程序,通過打印池用戶可以刪除、中止或者改變打印任務的優先級,在一個系統中只允許運行一個打印池對象,如果重復創建打印池則拋出異常。現使用單例模式來模擬實現打印池的設計。
六、優缺點分析
優點 | 描述 |
---|---|
? 節省資源 | 只創建一次實例,避免重復創建銷毀帶來的性能開銷 |
? 全局訪問 | 提供統一的訪問入口,便于集中管理 |
? 控制狀態一致性 | 避免多個實例導致的數據不一致問題 |
其他 | 允許可變數目的實例(多例類) |
缺點 | 描述 |
---|---|
? 違反單一職責原則 | 單例類通常承擔了太多責任,不符合高內聚低耦合原則 |
? 不利于測試 | 依賴全局狀態,難以進行單元測試 |
? 隱藏依賴關系 | 使用單例時不容易看出類之間的依賴關系 |
? 擴展困難 | 如果將來需要支持多個實例,重構成本較高 |
其他 | 由于自動垃圾回收機制,可能會導致共享的單例對象的狀態丟失 |
七、最終小結
單例模式是 Java 開發中最常用的設計模式之一,它的核心思想是:
“保證一個類只有一個實例,并提供全局訪問入口。”
作為一名 Java 開發工程師,掌握單例模式是非常有必要的,尤其是在開發工具類、配置管理器、緩存服務等組件時。
? 推薦使用方式總結:
寫法 | 是否推薦 | 適用場景 |
---|---|---|
枚舉 | ??? | 最佳選擇,防反射、線程安全 |
靜態內部類 | ?? | 常規項目首選 |
DCL | ? | 手動控制懶加載 |
餓漢式 | ?? | 類加載即初始化,適合簡單場景 |
懶漢式 | ? | 多線程下不安全,慎用 |
📌 一句話總結:
單例模式就像你家里的“總開關”,不管誰去按,都是控制同一個燈,確保系統的“唯一性”和“一致性”。
如果你正在構建一個需要全局統一管理的組件,不妨考慮使用單例模式。
部分內容由AI大模型生成,注意識別!