如何破壞單例模式?如何防止?
阿新 • • 發佈:2019-02-08
前幾天看到一道面試題,問的是單例的模式書寫以及單例模式能否被破壞?如何破壞?如何防止?
對於第一問單例模式的書寫,瞭解過設計模式的同學對單例模式絕對不會陌生。常用的單例模式有懶漢式、餓漢式兩種情況。實際的應用場景也是很常見,好比如資料庫連線池的設計,還有Windows的Task Manager(工作管理員)。
單例模式的書寫
這裡列出懶漢式的寫法
/**在多執行緒下如何保證單例模式需要記住三點。
*第一就是雙重加鎖機制。
*第二是確保SingleTest為volatile變數。
*第三,是我覺得最容易被遺忘的一點,就是建構函式為private。
*/
public class SingleTest {
public static volatile SingleTest singleTest ;
private SingleTest(){
}
public static SingleTest getSingleTest(){
if(singleTest==null){
synchronized(SingleTest.class){
if(singleTest==null){
singleTest = new SingleTest();
}
}
}
return singleTest;
}
}
單例模式的破壞
上面的寫法看起來沒問題,但是存在安全問題。因為我們可以通過反射破壞單例模式。
public static void main(String[] args) throws Exception{
Constructor constructor = SingleTest.class.getDeclaredConstructor();
constructor.setAccessible(true);
SingleTest s1 = SingleTest.getSingleTest ();
SingleTest s2 = SingleTest.getSingleTest();
SingleTest s3 = (SingleTest) constructor.newInstance();
System.out.println("輸出結果為:"+s1.hashCode()+"," +s2.hashCode()+","+s3.hashCode());
}
輸出結果為:1286131032,1286131032,1581347769
從輸出的結果我們就可以看出s1和s2為同一物件,s3為新物件。s3是我們通過反射機制,進而呼叫了私有的建構函式,然後產生了一個新的物件。
誒,既然單例模式存在漏洞,在JDK中那些應用單例模式的物件不是就會被惡意破壞了嘛。當時我也這樣想的,我嘗試了一下去破壞Runtime(在JDK為單例模式物件)。
public static void main(String[] args) throws Exception{
Constructor constructor1 = Runtime.class.getConstructor();
Runtime r1 = (Runtime) constructor1.newInstance();
Runtime r2 = Runtime.getRuntime();
Runtime r3 = Runtime.getRuntime();
System.out.println(r1.hashCode());
System.out.println(r2.hashCode());
System.out.println(r3.hashCode());
}
結果是被異常丟擲,檢視原始碼就看得出,JDK對已經封裝好的單例物件是有保護機制的。
防止單例模式的破壞
那麼我們如何去保護我們自定義的單例物件呢?很簡單,只需要修私有的建構函式即可。
private static boolean flag = false;
private SingleTest(){
synchronized(SingleTest.class){
if(flag == false){
flag = !flag;
}else {
throw new RuntimeException("單例模式被侵犯!");
}
}
}