文章目錄
- 簡介
- 問題
- 1. 確保一個類只有一個實例
- 2. 為該實例提供全局訪問點
- 解決方案
- 示例
- 重構前:
- 重構后:
- 拓展
- volatile 在單例模式中的雙重作用
- 總結
簡介
單例是一種創建型設計模式,它可以確保一個類只有一個實例,同時為該實例提供全局訪問點。
問題
單例模式同時解決了兩個問題:
1. 確保一個類只有一個實例
最常見的場景是控制對某些共享資源(例如數據庫或文件)的訪問。假設你已經創建了一個對象,又要創建一個相同類的對象。你不會得到一個新的對象,而是會得到你已經創建的對象。這種行為是無法通過常規構造函數實現,因為構造函數調用在設計上必須始終返回一個新對象。
2. 為該實例提供全局訪問點
全局變量非常方便,但很不安全,因為任何代碼都可能覆蓋這些變量的內容并使程序崩潰。單例模式類似全局變量,允許你從程序中的任何位置訪問某個對象。但是,它還可以保護這個實例不被其他代碼覆蓋。還有一點,為了不讓實現問題1 的代碼分散在各個地方,要把它限制在一個類中,特別是當你的其余代碼已經依賴了它的時候。
解決方案
所有 Singleton 的實現都有這樣兩個共同的步驟:
- 把默認構造函數設為私有,防止其他對象使用new創建它。
- 創建一個充當構造函數的靜態創建方法。這個方法會調用私有構造函數來創建一個對象并把它緩存在靜態字段中。這個方法的所有后續調用都會返回緩存好的對象。
如果你的代碼能訪問 Singleton 類,那么它就可以調用 Singleton 的靜態方法。無論何時調用該方法,都會返回相同的對象。
示例
數據庫連接
重構前:
class DBUtil {public Connection getConn() {return DriverManager.getConnection(URL); // 每次新建連接消耗500ms+}
}// 調用端
new DBUtil().getConn().execute("SELECT...");
new DBUtil().getConn().execute("UPDATE..."); // 產生兩個獨立連接
重構后:
public class Database {private static volatile Database instance; private Connection connection;// 私有化構造并建立物理連接private Database() {this.connection = DriverManager.getConnection(JDBC_URL); // 真實連接建立}// 雙重檢查鎖定實現線程安全public static Database getInstance() {if (instance == null) { synchronized (Database.class) {if (instance == null) {instance = new Database(); }}}return instance;}// 統一入口方法(可擴展緩存邏輯)public ResultSet query(String sql) {return connection.createStatement().executeQuery(sql); // 所有SQL通過單連接執行}
}
拓展
volatile 在單例模式中的雙重作用
- 可見性保證(Visibility)
阻止線程的本地緩存與主內存數據不同步,確保所有線程讀取到的是最新實例狀態。 - 禁止指令重排序(Happens-Before)
消除 JVM 級別可能的危險優化(非原子化對象構造的三步指令):
未加 volatile 時的風險時序:
A線程: 分配內存 → 寫入未初始化的對象引用(指令排序導致)
B線程: 獲取到非空引用 → 訪問未完成初始化的對象(空指針異常)volatile 強制時序:
分配內存 → 初始化對象 → 寫入引用(三步驟原子性可見)
總結
- 單例(Sin-gle-ton)類:聲明了一個叫做get-Instance獲的靜態方法來返回實例。單例的構造函數必須為私有。調用獲取實例方法必須是獲取單例對象的唯一方式。