在軟體開發中,我們經常遇到這類需求:整個應用程式生命週期中,某個類別只能有一個實例。典型場景包括資料庫連線池、系統配置管理、日誌記錄器,以及 Java 標準庫中的 Runtime、Calendar 等。若不加控制,隨意建立多個實例會導致資源浪費、狀態不一致甚至併發衝突。單例模式正是為了解決此問題而生的經典設計模式。
早期開發者常透過一個「全域物件」類別(如 GlobalObject)持有目標實例,並提供存取方法。雖然能勉強運作,但存在明顯缺點:
GlobalObject 本質上是 C 風格的全域變數,破壞封裝性,增加耦合。單例模式核心在於三點:私有建構子、私有靜態實例變數、公有靜態取得實例方法。以下以資料庫連線池管理器為例:
public class ConnectionPoolManager {
// 1. 私有靜態實例,初始為 null
private static ConnectionPoolManager instance = null;
// 2. 私有建構子,防止外部實例化
private ConnectionPoolManager() {
// 初始化工作,如建立連線池
}
// 3. 公有靜態工廠方法,保證單一實例
public static synchronized ConnectionPoolManager getInstance() {
if (instance == null) {
instance = new ConnectionPoolManager();
}
return instance;
}
public Connection getConnection() {
// 取得連線邏輯
return null;
}
}
new 建立實例,唯一實例化入口在 getInstance()。getInstance() 時才建立實例,節省資源。注意方法加上 synchronized 保證執行緒安全(或使用雙重檢查鎖、靜態內部類等進階寫法)。Java API 中的 java.lang.Runtime 採用更簡潔的「餓漢式」寫法:
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
// 禁止外部實例化
private Runtime() {}
}
此方式在類別載入時即建立實例,天然執行緒安全,無同步開銷,適用於實例建立成本低、必須使用的場景。
public class QueryFunctions {
public static Collection findStudentByName(String name) {
// 透過靜態工廠方法取得唯一實例
ConnectionPoolManager pool = ConnectionPoolManager.getInstance();
Connection conn = pool.getConnection();
// 執行查詢...
return null;
}
}
單例模式雖簡單,但實作細節決定品質:
synchronized)、雙重檢查鎖(需 volatile 修飾實例變數)、靜態內部類(推薦,兼具延遲載入與執行緒安全)、或枚舉單例(最簡潔,防反射/序列化破壞)。掌握單例模式的各種變體與適用場景,是 Java 開發者進階的必修課。