如何破壞單例

首先提供一個雙重加鎖的單例模式

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")
   }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章