目錄
1、核心思想
2、實現方式
2.1 餓漢式
2.2 懶漢式
2.3 枚舉(Enum)
3、關鍵注意事項
3.1?線程安全
3.2?反射攻擊
3.3 序列化與反序列化
3.4 克隆保護
4、適用場景
1、核心思想
目的:確保一個類僅有一個實例
功能:供全局訪問點(通過靜態方法或變量提供全局訪問入口),可以選擇延遲加載。
優點 | 缺點 |
---|---|
嚴格控制實例數量,節省資源 | 可能隱藏類之間的依賴關系,降低可測試性 |
全局訪問點方便管理共享資源 | 違背單一職責原則(管理自身生命周期) |
避免頻繁創建銷毀對象 | 多線程環境需額外處理同步問題 |
2、實現方式
2.1 餓漢式
餓漢式:即在初始階段就主動進行實例化,并時刻保持一種渴求的狀態,無論此單例是否有人使用。
特點:類加載時立即創建實例,線程安全但可能浪費資源。
public class EagerSingleton {// static:在類加載時初始化,與類同在; final:一旦被賦值不能被更改private static final EagerSingleton instance = new EagerSingleton();// 私有構造函數,禁止外部調用創建對象private EagerSingleton() {} public static EagerSingleton getInstance() {return instance;}
}
2.2 懶漢式
懶漢式:在第一次使用時,再進行初始化,防止沒有人調用,提前初始化造成的資源浪費。
特點:延遲實例化,需處理線程安全問題。
1> 同步鎖版本(不推薦)
public class SynchronizedSingleton {private static SynchronizedSingleton instance;private SynchronizedSingleton() {}public static synchronized SynchronizedSingleton getInstance() {if (instance == null) {instance = new SynchronizedSingleton();}return instance;}
}
注意: 變量不能使用final,會導致被初始化為null,之后不可賦值
缺點:線程還沒進入方法內,就先加鎖排隊,造成線程阻塞,資源和時間的浪費。
2> 雙重檢查鎖(Double-Checked Locking)
public class DCLSingleton {// volatile:防止指令重排序,保證變量值在各線程訪問時的同步性、唯一性private static volatile DCLSingleton instance;private DCLSingleton() {}public static DCLSingleton getInstance() {if (instance == null) { // 第一次檢查,放寬入口,保證線程并發高效性synchronized (DCLSingleton.class) {if (instance == null) { // 第二次檢查,加鎖同步,保證實例化單次運行instance = new DCLSingleton();}}}return instance;}
}
相比“懶漢模式”?,其實在大多數情況下我們通常會更多地使用“餓漢模式”?,原因在于這個單例遲早是要被實例化占用內存的,延遲懶加載的意義并不大,加鎖解鎖反而是一種資源浪費,同步更是會降低CPU的利用率,使用不當的話反而會帶來不必要的風險。
2.3 枚舉(Enum)
特點:天然防止反射和序列化攻擊,線程安全(推薦方式)
public enum EnumSingleton {INSTANCE; // 這是一個單例對象public void doSomething() {// 業務方法}
}
3、關鍵注意事項
3.1?線程安全
多線程環境下需確保實例唯一性(如雙重檢查鎖、枚舉等)。
3.2?反射攻擊
通過反射可繞過私有構造函數,需額外防護(如枚舉或拋出異常)。
攻擊示例:
// 反射攻擊代碼:
Class<?> clazz = Singleton.class;
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true); // 繞過 private 權限
Singleton hackedInstance = (Singleton) constructor.newInstance(); // 創建新實例!
防御方法:在構造方法中拋異常(檢測是否已存在實例,若存在則拋出異常)
private Singleton() {if (INSTANCE != null) {throw new IllegalStateException("單例已被創建,禁止反射調用!");}
}
3.3 序列化與反序列化
反序列化可能創建新實例,需實現?readResolve()
?方法。
攻擊示例:
// 序列化攻擊代碼:
Singleton instance1 = Singleton.getInstance();
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(instance1);
// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Singleton instance2 = (Singleton) ois.readObject(); // 新實例!
防御方法:實現?readResolve()
?方法,直接返回已有實例,覆蓋反序列化邏輯。
public class Singleton implements Serializable {private static final Singleton INSTANCE = new Singleton();private Singleton() {}// 反序列化時直接返回 INSTANCEprotected Object readResolve() {return INSTANCE;}
}
3.4 克隆保護
重寫?clone()
?方法并拋出異常。
若單例類實現了?Cloneable
?接口,并直接使用默認的?clone()
?方法(或自定義實現未做防御),攻擊者可以通過調用?clone()
?創建新實例,破壞單例的唯一性。
如果類不實現?Cloneable
?接口,調用?clone()
?會拋出?CloneNotSupportedException。
即使類未實現?Cloneable
,也可顯式重寫?clone()
?方法禁止克隆:
public class Singleton {private static final Singleton INSTANCE = new Singleton();private Singleton() {}public static Singleton getInstance() {return INSTANCE;}// 防御 clone 攻擊@Overrideprotected Object clone() throws CloneNotSupportedException {throw new CloneNotSupportedException("禁止克隆單例!");}
}
4、適用場景
-
資源共享:如數據庫連接池、線程池。
-
配置管理:全局配置類避免重復加載。
-
日志記錄:統一日志寫入入口。
-
設備驅動:如打印機唯一控制實例。