文章目錄
- 1 基本介紹
- 2 實現方式
- 2.1 餓漢式
- 2.1.1 代碼
- 2.1.2 特性
- 2.2 懶漢式 ( 線程不安全 )
- 2.2.1 代碼
- 2.2.2 特性
- 2.3 懶漢式 ( 線程安全 )
- 2.3.1 代碼
- 2.3.2 特性
- 2.4 雙重檢查
- 2.4.1 代碼
- 2.4.2 特性
- 2.5 靜態內部類
- 2.5.1 代碼
- 2.5.2 特性
- 2.6 枚舉
- 2.6.1 代碼
- 2.6.2 特性
- 3 實現的要點
- 4 線程不安全的單例模式
- 4.1 代碼
- 4.2 評價
- 5 JDK中的單例模式
- 6 單例模式的類圖及角色
- 6.1 類圖
- 6.2 角色
- 7 推薦的單例模式的實現
- 8 單例模式的使用場景
1 基本介紹
單例模式(Singleton Pattern)是一種常用的軟件設計模式,其目的是 確保一個類僅有一個實例,并提供一個 靜態方法 來獲取該實例。
2 實現方式
單例模式圍繞著兩個特性展開:
- 延遲加載:在需要這個單例時才創建單例,避免浪費內存。
- 線程安全:在多線程環境下,保證多線程使用的單例是同一個單例。
共有以下六種實現方式:
2.1 餓漢式
2.1.1 代碼
餓漢式的實現在 Java 中有兩種實現,常用的是第一種。
方式一:給靜態常量賦初始值。
public class Singleton {private static final Singleton INSTANCE = new Singleton();private Singleton() {}public static synchronized Singleton getInstance() {return INSTANCE;}
}
方式二:使用靜態代碼塊賦值。
public class Singleton {private static final Singleton INSTANCE;static {INSTANCE = new Singleton();}private Singleton() {}public static synchronized Singleton getInstance() {return INSTANCE;}
}
2.1.2 特性
- 不延遲加載:在 類加載 時就創建單例。
- 線程安全:類加載由 JVM 保證線程安全,所以此時創建的單例也是線程安全的。
2.2 懶漢式 ( 線程不安全 )
2.2.1 代碼
public class Singleton {private static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}
2.2.2 特性
- 延遲加載:只有在 調用獲取單例的方法
getInstance()
時才創建單例。 - 線程不安全:如果有多個線程同時通過了
if (singleton == null)
這個條件,則它們會創建多個單例。
2.3 懶漢式 ( 線程安全 )
2.3.1 代碼
注意 getInstance()
方法前的 synchronized
修飾符。
public class Singleton {private static Singleton instance;private Singleton() {}public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}
2.3.2 特性
- 延遲加載:只有在 調用獲取單例的方法
getInstance()
時才創建單例。 - 線程安全:在多個線程同時獲取單例時,使用
synchronized
互斥鎖,來保證只有一個線程能夠生成單例,而其他線程等待這個線程創建的單例,保證了單例的線程安全。 - 成本太大:即使已經有單例了,每次調用
getInstance()
方法還得經過 加鎖 和 釋放鎖 的流程(因為使用了synchronized
互斥鎖),降低了并發性能。
2.4 雙重檢查
2.4.1 代碼
注意單例前的 volatile
修飾符,它有兩個作用:保證變量對所有線程可見 和 防止指令重排。在此處起 防止指令重排 的作用:防止 JIT 即時編譯器對 instance = new Singleton();
這行代碼進行重排序。
如果進行重排序,則可能先給 instance
分配內存(此時 instance != null
),然后才調用構造器為 instance
的屬性賦值。在這兩步操作之間,要是有線程調用 getInstance()
方法,它將無法通過外層的 instance == null
條件,會返回一個不完整(賦值不完全)的對象。
public class Singleton {private static volatile Singleton instance; // 注意 volatile 在這里起 防止指令重排 的作用private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}}
2.4.2 特性
- 延遲加載:只有在 調用獲取單例的方法
getInstance()
時才創建單例。 - 線程安全:在多個線程同時獲取單例 且 單例未創建時,如果都通過了外層的
instance == null
條件,則在內層使用synchronized
互斥鎖,來保證只有一個線程創建單例,而其他線程等待這個線程創建的單例,保證了單例的線程安全。 - 成本小:這種實現方式只有最初創建單例時會加互斥鎖,之后就不需要創建單例了,直接返回即可,無需加鎖和釋放鎖,提高了并發性能。
2.5 靜態內部類
2.5.1 代碼
public class Singleton {private Singleton() {}private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}
2.5.2 特性
- 延遲加載:只有在 調用獲取單例的方法
getInstance()
時,才會 觸發靜態內部類的加載,從而創建單例。 - 線程安全:靜態內部類也是類,類加載由 JVM 保證其線程安全,所以本單例是線程安全的。
2.6 枚舉
2.6.1 代碼
public enum Singleton {INSTANCE; // 直接使用 Singleton.INSTANCE 就可以獲取到單例// 可以隨意寫方法和屬性,就像在類中一樣
}
2.6.2 特性
- 不延遲加載:在 類加載 時就創建單例。
- 線程安全:類加載由 JVM 保證線程安全,所以此時創建的單例也是線程安全的。
- 防止 反射 或 反序列化 破壞單例:其他單例的實現都可以通過 反射 或 反序列化 的方式重新創建新的單例,唯獨本實現無法使用這兩種方式重新創建新的單例,這是因為 枚舉無法通過反射獲取對象,并且 枚舉在序列化和反序列化時不會調用構造器。所以這種實現是 最推薦的。
3 實現的要點
- 構造器私有化。例如
private Singleton() {}
。 - 類中有一個 靜態 的單例屬性。
- 提供一個 靜態 方法來獲取上述單例。
4 線程不安全的單例模式
4.1 代碼
public class Singleton {private static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {instance = new Singleton();}}return instance;}
}
4.2 評價
這樣的單例模式是 線程不安全 的,synchronized
互斥鎖沒有起到應有的作用。只要多個線程都能通過 instance == null
條件,則它們每個線程都會創建一次單例,synchronized
僅僅能保證同一時刻只有一個線程在創建單例罷了。
應該將 判斷 和 賦值 都放到 synchronized
互斥鎖里,就像單例的 第三種實現——懶漢式 ( 線程安全 ) 一樣。
5 JDK中的單例模式
在JDK中,Runtime
類使用了 餓漢式單例,代碼如下:
public class Runtime {private static final Runtime currentRuntime = new Runtime();public static Runtime getRuntime() {return currentRuntime;}private Runtime() {}// ...
}
6 單例模式的類圖及角色
6.1 類圖
其中,singleton
屬性和 Singleton()
構造器都是 私有的,而 getInstance()
方法是 公開的。此外,singleton
屬性和 getInstance()
方法都是 靜態的。
6.2 角色
在單例模式中,只有一個角色 Singleton
,它負責 實現返回單例的 靜態 方法。
7 推薦的單例模式的實現
- 餓漢式
- 雙重檢查
- 靜態內部類
- 枚舉
8 單例模式的使用場景
- 創建對象耗時過多或耗費資源過多(重量級),但經常用到。
- 頻繁訪問 數據庫 或 文件 的對象。
- 工具類對象。