單例模式的實現方式有很多種,詳情可以參考單例模式的7種實現方式及分析,從線程安全以及懶加載等角度來看其中第6種(double check)和第7種(靜態內部類)的實現方式都是值得推薦並且應用廣泛的,但是它們(包括第1到第7種)都有一個痛點,就是無法阻止通過反射或者序列化來破解單例對象的唯一性
反射破解
下列代碼以double check方式實現的單例模式爲示例,詳情如下:
- 代碼
public class SyncDoubleCheckLazy {
private SyncDoubleCheckLazy() {
}
private static SyncDoubleCheckLazy singleton = null;
public static SyncDoubleCheckLazy getSingleton() {
if (singleton == null) {
synchronized (SyncDoubleCheckLazy.class) {
if (singleton == null) {
singleton = new SyncDoubleCheckLazy();
}
}
}
return singleton;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//通過getSingleton方法獲取的單例對象
SyncDoubleCheckLazy originalSingleton = SyncDoubleCheckLazy.getSingleton();
//通過反射創建的非單例對象
//這裏插播一條小知識,getConstructor無法獲取到私有的構造方法,因爲getConstructor只返回聲明爲public的構造方法,
//但是getDeclaredConstructor可以,這個方法會返回所有構造器,包括public的和非public的;
//這個規則同樣適用於其它反射操作;
SyncDoubleCheckLazy newSingleton = SyncDoubleCheckLazy.class.getDeclaredConstructor().newInstance();
//打印結果看看這兩個對象是否是一個
System.out.println(originalSingleton == newSingleton);
}
}
- 打印結果
false
序列化破解
下列代碼以double check方式實現的單例模式爲示例,詳情如下:
- 代碼
public class SyncDoubleCheckLazy implements Serializable {
private SyncDoubleCheckLazy() {
}
private static SyncDoubleCheckLazy singleton = null;
public static SyncDoubleCheckLazy getSingleton() {
if (singleton == null) {
synchronized (SyncDoubleCheckLazy.class) {
if (singleton == null) {
singleton = new SyncDoubleCheckLazy();
}
}
}
return singleton;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
//通過getSingleton方法獲取的單例對象
SyncDoubleCheckLazy originalSingleton = SyncDoubleCheckLazy.getSingleton();
//通過序列化創建的非單例對象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("newSingleton"));
oos.writeObject(originalSingleton);
oos.flush();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("newSingleton"));
SyncDoubleCheckLazy newSingleton = (SyncDoubleCheckLazy) ois.readObject();
ois.close();
//打印結果看看這兩個對象是否是一個
System.out.println(originalSingleton == newSingleton);
}
}
- 打印結果
false
經過前面的鋪墊之後,我們接下來就進入我們的正題了,即枚舉單例模式以及它是如何避免反射和序列化來破解單例模式的;
枚舉單例代碼實現
public enum EnumSingleton {
INSTANCE;
}
反射破解
- 代碼
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//直接獲取原單例對象
EnumSingleton originalSingleton = EnumSingleton.INSTANCE;
//通過反射創建的非單例對象
EnumSingleton newSingleton = EnumSingleton.class.getDeclaredConstructor().newInstance();
//打印結果看看這兩個對象是否是一個
System.out.println(originalSingleton == newSingleton);
}
- 打印結果
Exception in thread "main" java.lang.NoSuchMethodException: org.jc.framework.javasupport.EnumSingleton.<init>(int)
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at org.jc.framework.javasupport.EnumSingleton.main(EnumSingleton.java:16)
通過以上測試我們知道枚舉類型在java中是無法通過反射來創建的,否則會拋異常;
序列化破解
- 代碼
public static void main(String[] args) throws IOException, ClassNotFoundException {
//直接獲取原單例對象
EnumSingleton originalSingleton = EnumSingleton.INSTANCE;
//通過序列化創建的非單例對象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("newSingleton"));
oos.writeObject(originalSingleton);
oos.flush();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("newSingleton"));
EnumSingleton newSingleton = (EnumSingleton) ois.readObject();
ois.close();
//打印結果看看這兩個對象是否是一個
System.out.println(originalSingleton == newSingleton);
}
- 打印結果
true
在序列化的時候Java僅僅是將枚舉對象的name屬性輸出到結果中,反序列化的時候是通過java.lang.Enum的valueOf方法來根據名字查找枚舉對象。同時,編譯器是不允許任何對這種序列化機制的定製的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法;
其次枚舉單例的線程安全性是通過jvm的類加載機制來保證的,無需我們操心,但是對我來說,枚舉單例的懶加載是值得商榷的一點,如果枚舉中定義了靜態變量/常量/方法的話,那麼當使用的時候可能會破壞單例的懶加載!