簡介
單例模式是一種常用的軟體設計模式,用于建立類型。通過單例模式的方法建立的類在目前程序中隻有一個執行個體。單例模式的類隻能允許一個執行個體存在。單例模式的作用是保證在整個應用程式的生命周期中,任何一個時刻,單例類的執行個體都隻存在一個。
組成部分:
- 私有化構造方法。
- 私有化内部執行個體。
- 公有靜态方法用來擷取内部執行個體。
單例模式
優缺點
單例模式的優點有:
- 提供了對唯一執行個體的受控通路,可以保證對象的唯一性和一緻性。
- 減少了記憶體開銷,避免了頻繁的建立和銷毀對象。
- 避免了對資源的多重占用,例如檔案操作、資料庫連接配接等。
單例模式的缺點有:
- 不支援繼承和多态,違反了單一職責原則,一個類應該隻關心内部邏輯,而不關心外部如何執行個體化。
- 不易擴充,如果需要建立多個執行個體,就需要修改代碼,違反了開閉原則,一個類應該對擴充開放,對修改關閉。
- 不支援有參數的構造函數,如果需要傳遞參數,就需要修改方法或者定義其他方法。
- 可能存在反射或者反序列化攻擊,破壞單例的唯一性。
應用場景
單例模式适用于以下場景:
- 需要頻繁建立和銷毀的對象,例如緩存、線程池、系統資料庫等。
- 需要控制資源的通路,例如檔案操作、資料庫連接配接等。
- 需要保證對象的唯一性和一緻性,例如配置資訊、全局變量等。
Java 代碼示例
在 Java 中,有五種不同的單例實作方法。其中包括餓漢式、懶漢式、雙檢鎖、靜态内部類和枚舉類。 單例模式的五種實作原理分别是餓漢式、懶漢式、雙重檢測、靜态内部類和枚舉類。它們各自的優缺點如下:
- 餓漢式:原理是在類加載的時候,就建立并初始化一個靜态的執行個體對象,然後通過一個靜态的方法傳回這個執行個體。優點是線程安全,不需要加鎖;缺點是不支援延遲加載,可能會浪費資源。
public class Singleton {
private Singleton() {}
private static Singleton instance;
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 懶漢式:原理是在第一次調用擷取執行個體的方法時,才建立并初始化一個靜态的執行個體對象,然後傳回這個執行個體。為了保證線程安全,需要給擷取執行個體的方法加上synchronized關鍵字。優點是支援延遲加載,節省資源;缺點是線程不安全,需要加鎖,影響性能。
public class Singleton {
private Singleton() {}
private static final Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
- 雙重檢測:原理是在第一次調用擷取執行個體的方法時,先判斷靜态的執行個體對象是否為空,如果為空,則進入同步代碼塊,再判斷一次是否為空,如果為空,則建立并初始化一個靜态的執行個體對象,然後傳回這個執行個體。為了防止指令重排序導緻空指針異常,需要給靜态的執行個體對象加上volatile關鍵字。優點是線程安全,支援延遲加載,不需要加鎖;缺點是可能會出現空指針異常,需要使用 volatile 關鍵字防止指令重排序。
public class Singleton {
private Singleton() {}
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
- 靜态内部類:原理是利用了 Java 靜态内部類的特性,即外部類加載時不會加載内部類,隻有在使用到内部類時才會加載。是以,在第一次調用擷取執行個體的方法時,才會加載靜态内部類,并建立并初始化一個靜态的執行個體對象,然後傳回這個執行個體。優點是線程安全,支援延遲加載,不需要加鎖;缺點是不能防止反射或者反序列化攻擊。
public class Singleton {
private Singleton() {}
private static class Instance {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return Instance.instance;
}
}
- 枚舉類:原理是利用了Java枚舉類型本身的特性,即枚舉類型在加載時就會建立所有的枚舉常量,并且保證了線程安全性和唯一性。是以,在調用擷取執行個體的方法時,直接傳回枚舉常量即可。優點是線程安全,簡單易用,可以防止反射或者反序列化攻擊;缺點是不支援延遲加載,不能繼承其他類。
public enum Singleton {
INSTANCE;
}
這些不同的實作方式有不同的适用場景,需要根據具體的需求和條件來選擇。在這裡,我隻能給出一些個人的看法,僅供參考。
- 如果對記憶體資源比較敏感,或者單例對象不需要頻繁使用,可以考慮使用懶漢式或者雙重檢測,因為它們支援延遲加載,可以節省資源。
- 如果對性能比較敏感,或者單例對象需要頻繁使用,可以考慮使用餓漢式或者靜态内部類,因為它們不需要加鎖,可以提高效率。
- 如果對安全性比較敏感,或者需要防止反射或者反序列化攻擊,可以考慮使用枚舉類,因為它可以保證執行個體的唯一性和不可變性。
- 如果對簡潔性比較敏感,或者不需要繼承其他類,可以考慮使用枚舉類,因為它是最簡單的實作方式。
個人來說在編碼效率和可維護性上我比較傾向于使用靜态内部類的實作方式,既能保證線程安全性,又能支援延遲加載。
Spring 代碼示例
在 Spring 架構中,Spring 預設使用單例模式來建立和管理 Bean 對象,但是可以通過 @Scope("singleton") 注解來指定 Bean 對象的作用域。
- @Scope("singleton"):表示該Bean對象是一個單例對象,在整個Spring容器中隻有一個執行個體。
- @Scope("prototype"):表示該Bean對象是一個原型對象,在每次請求時都會建立一個新的執行個體。
- @Scope("request"):表示該Bean對象的作用域是一個HTTP請求,在同一個請求中隻有一個執行個體。
- @Scope("session"):表示該Bean對象的作用域是一個HTTP會話,在同一個會話中隻有一個執行個體。
總結
單例模式是一種簡單而常用的設計模式,它可以保證一個類隻有一個執行個體,并提供一個全局通路點。單例模式有多種實作方式,各有優缺點。單例模式可以節約系統資源,避免資源沖突,保證對象的唯一性和一緻性。但是單例模式也有不利于繼承和擴充的缺點,以及可能存在的安全隐患。在使用單例模式時,需要根據具體情況和需求選擇合适的方法,并注意避免潛在的問題。