1. 程式人生 > >單例模式的破壞及任何防止被破壞

單例模式的破壞及任何防止被破壞

        常用的單例模式有懶漢式、餓漢式兩種情況。實際的應用場景也是很常見,好比如資料庫連線池的設計,還有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;
	}
	
	
}