單例模式的破壞及任何防止被破壞
常用的單例模式有懶漢式、餓漢式兩種情況。實際的應用場景也是很常見,好比如資料庫連線池的設計,還有Windows的Task Manager(工作管理員)等。
所謂單例模式就是,某一個類只能有一個例項,實現的核心就是將類的建構函式私有化,只能由該類建立物件,其他物件就不能呼叫該類的建構函式,即不能建立物件了。
現在看一個問題:物件的建立方式有哪幾種?
四種:new 、克隆、序列化、反射。
其實上面的說法有點問題,改為:……其他物件就不能呼叫該類的建構函式,即不能通過new 來建立物件了。那麼是否還有可以通過其他的三種方式建立物件呢,即其他三種方式會不會破壞單例模式呢?
克隆可以對單例模式的破壞
由克隆我們可以想到原型模式,原型模式就是通過clone方法實現物件的建立的,clone方式是Object方法,每個物件都有,那麼使用一個單例模式類的物件,呼叫clone方法,再建立一個新的物件了,那豈不是上面說的單例模式失效了。當然答案是否定,某一個物件直接呼叫clone方法,會丟擲異常,即並不能成功克隆一個物件。呼叫該方法時,必須實現一個Cloneable 介面。這也就是原型模式的實現方式。還有即如果該類實現了cloneable介面,儘管建構函式是私有的,他也可以建立一個物件。即clone方法是不會呼叫建構函式的,他是直接從記憶體中copy記憶體區域的。
解決辦法:單例模式的類不實現cloneable介面。
序列化可以對單例模式的破壞
一是可以實現資料的持久化;二是可以物件資料的遠端傳輸。 如果過該類implements Serializable,那麼就會在反序列化的過程中再創一個物件。
/** * * @author 小欽 *懶漢式 */ public class Singleton implements Serializable { private static volatile Singleton instance; private Singleton(){} public static Singleton getInstance(){ if(instance==null){ synchronized (Singleton.class) { if(instance==null){ instance=new Singleton(); } } } return instance; } }
測試類:
public class SerializableDemo1 {
//為了便於理解,忽略關閉流操作及刪除檔案操作。真正編碼時千萬不要忘記
//Exception直接丟擲
public static void main(String[] args) throws IOException, ClassNotFoundException {
//Write Obj to file
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("testFile"));
oos.writeObject(Singleton.getInstance());
//Read Obj from file
File file=new File("testFile");
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(file));
Singleton newInstance=(Singleton)ois.readObject();
//判斷是否是同一個物件
System.out.println(newInstance==Singleton.getInstance());
}
}
//輸出的是false
序列化會通過反射呼叫無引數的構造方法建立一個新的物件。
通過對Singleton的序列化與反序列化得到的物件是一個新的物件,這就破壞了Singleton的單例性。
解決辦法:在反序列化時,指定反序化的物件例項。即只要在Singleton類中定義readResolve
就可以解決該問題:
public class Singleton implements Serializable {
private static volatile Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class) {
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
private Object readResolve(){
return instance;
}
}
主要在Singleton中定義readResolve方法,並在該方法中指定要返回的物件的生成策略,就可以防止單例被破壞。
反射可以對單例模式的破壞
反射是可以獲取類的建構函式,再加一行 setAccessible(true);就可以呼叫私有的建構函式,建立物件了。
/**
*
* @author 小欽
*懶漢式
*/
public class Singleton {
private static volatile Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class) {
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}
測試類:
public class TestDemo {
public static void main(String[] args) throws Exception, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
Constructor constructor=Singleton.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
Singleton s3 =(Singleton) constructor.newInstance(null);
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s3.hashCode());
}
}
//輸出:366712642
366712642
1829164700
解決辦法:當第二次呼叫建構函式時丟擲異常。
/**
*
* @author 小欽
*懶漢式
*/
public class Singleton {
private static volatile Singleton instance;
private static boolean flag=true;
private Singleton(){
if(flag){
flag=false;
}
else {
throw new RuntimeException("單例模式遇到攻擊,第二個物件未建立成功");
}
}
public static Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class) {
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}