單例模式(Singleton)
概念:
單例模式 確保一個類只有一個實例,並提供一個全局訪問點。
經典單例:
public class Singleton {
private static Singleton uniqueInstance;
//構造函數設定爲 private,避免被創建
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
//延遲初始化
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
延遲初始化雖然能避免資源浪費的情況,但是上面的單例模式在多線程情況下就可能出現錯誤,多線程同時執行到 if 語句時,可能會出現 new 兩個單例的情況。
不使用延遲初始化:
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
}
非延遲初始化的做法在 JVM 加載這個類時馬上創建唯一的單例。JVM 保證任何線程訪問 uniqueInstance 靜態變量之前,一定先創建此實例。
但如果程序中並沒有用到單例,提前創建好很可能會浪費資源。
多線程單例模式:
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {}
//synchronized 保證多線程順序訪問
public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
通過加入 synchronized 我們保證每個線程在訪問該方法時必須保證其他線程離開。保證了多線程情況下的安全。但 synchronized 同步可能會影響性能,在性能要求不苛刻的情況下我們可以選擇這種單例。
雙重檢查鎖單例模式
public class Singleton {
//注意 volatile 是必須的,否則 JVM 可能會進行優化(指令重排)導致錯誤
private volatile static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
if (uniqueInstance == null) {
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
volatile 作用:這個變量不會在多個線程中存在複本,直接從內存讀取。這個關鍵字會禁止指令重排序優化。也就是說,在 volatile 變量的賦值操作後面會有一個內存屏障(生成的彙編代碼上),讀操作不會被重排序到內存屏障之前。
雙重檢查鎖 模式看着很完美,實際上還有些小問題,就是在早期版本下的 volatile 可能會導致該方式失敗。
優雅版本:
public class Singleton {
//靜態內部類,
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
只有在調用 getInstance 時對象纔會被創建,同時沒有性能缺點,也不依賴 Java 版本。
枚舉單例:
public enum Singleton {
INSTANCE;
public static Singleton getInstance() {
return INSTANCE;
}
}
優點:非常簡單。
默認枚舉實例的創建是線程安全的,所以不需要擔心線程安全的問題
適用場景:
- 系統只需要一個實例對象,如系統要求提供一個唯一的序列號生成器,或者需要考慮資源消耗太大而只允許創建一個對象。
- 客戶調用類的單個實例只允許使用一個公共訪問點,除了該公共訪問點,不能通過其他途徑訪問該實例。
- 在一個系統中要求一個類只有一個實例時才應當使用單例模式。反過來,如果一個類可以有幾個實例共存,就需要對單例模式進行改進,使之成爲多例模式
優缺點:
優點:
- 提供了對唯一實例的受控訪問。因爲單例類封裝了它的唯一實例,所以它可以嚴格控制客戶怎樣以及何時訪問它,併爲設計及開發團隊提供了共享的概念。
- 由於在系統內存中只存在一個對象,因此可以節約系統資源,對於一些需要頻繁創建和銷燬的對象,單例模式無疑可以提高系統的性能。
- 允許可變數目的實例。我們可以基於單例模式進行擴展,使用與單例控制相似的方法來獲得指定個數的對象實例。
缺點:
- 由於單例模式中沒有抽象層,因此單例類的擴展有很大的困難。
- 單例類的職責過重,在一定程度上違背了“單一職責原則”。因爲單例類既充當了工廠角色,提供了工廠方法,同時又充當了產品角色,包含一些業務方法,將產品的創建和產品的本身的功能融合到一起。
- 濫用單例將帶來一些負面問題,如爲了節省資源將數據庫連接池對象設計爲單例類,可能會導致共享連接池對象的程序過多而出現連接池溢出;現在很多面嚮對象語言(如Java、C#)的運行環境都提供了自動垃圾回收的技術,因此,如果實例化的對象長時間不被利用,系統會認爲它是垃圾,會自動銷燬並回收資源,下次利用時又將重新實例化,這將導致對象狀態的丟失。
參考:
Head Frist 設計模式