首先提供一個雙重加鎖的單例模式
public class CheckSingleton{
private CheckSingleton(){};
private volatile static CheckSingleton checksingleton;
public static CheckSingleton getInstance(){
if(null==checksingleton){
synchronized(CheckSingleton.class){
if(null==checksingleton){
checksingleton = new CheckSingleton();
}
}
}
return checksingleton;
}
}
一、【反射破壞單例】
import java.lang.reflect.Constructor;
public class SingletonTest {
public static void main(String[] args) {
CheckSingleton singleton = CheckSingleton.getSingleton();
try {
Class<CheckSingleton> singleClass = (Class<Singleton>)Class.forName("com.dev.interview.CheckSingleton");
Constructor<CheckSingleton> constructor = singleClass.getDeclaredConstructor(null);
constructor.setAccessible(true);
CheckSingleton singletonByReflect = constructor.newInstance();
System.out.println("singleton : " + singleton);
System.out.println("singletonByReflect : " + singletonByReflect);
System.out.println("singleton == singletonByReflect : " + (singleton == singletonByReflect));
} catch (Exception e) {
e.printStackTrace();
}
}
}
發現發射生成的一個新的對象 ,此時單例模式被破壞
防止方案(設置爲如果通過反射創建則拋出異常):
private Singleton() {
if (null!= CheckSingleton) {
throw new RuntimeException("Singleton constructor is called... ");
}
}
二、反序列化生成新的實例
public class SingletonTest {
public static void main(String[] args) {
CheckSingleton singleton = CheckSingleton.getSingleton();
//Write Obj to file
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(singleton);
//Read Obj from file
File file = new File("tempFile");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Singleton singletonBySerialize = (CheckSingleton)ois.readObject();
//判斷是否是同一個對象
System.out.println("singleton : " + singleton);
System.out.println("singletonBySerialize : " + singletonBySerialize);
System.out.println("singleton == singletonBySerialize : " + (singleton == singletonBySerialize));
} catch (Exception e) {
e.printStackTrace();
}
}
}
通過先序列化再反序列化的方式,可獲取到一個新的單例對象,這就破壞了單例。
避免方案(在單例中加入readResolve方法,因爲在反序列化執行過程中會執行到ObjectInputStream#readOrdinaryObject方法,這個方法會判斷對象是否包含readResolve方法,如果包含的話會直接調用這個方法獲得對象實例。)
private Object readResolve() {
return getSingleton();
}
如果沒有該方法,會通過反序列化中特殊的反射方式得到對象,和上邊第一種破壞方式中的反射不一樣。
三、建議使用枚舉做單例模式,天生安全。
public enum Singleton{
INSTANCE;
public void say(){
System.out.print("hello ,enum")
}
}