面試題——單例模式與反射、序列化

餓漢式

立即加載,線程安全。

public class Singleton {
    private static Singleton INSTANCE = new Singleton();//立即加載
    private Singleton(){};
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

懶漢式

延遲加載,線程不安全。

public class Singleton {
    private static Singleton INSTANCE;//延遲加載
    private Singleton(){};
    public static Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        } 
        return INSTANCE;
    }
}
雙檢鎖(DCL)

延遲加載,線程安全。

public class Singleton {
    private volatile static Singleton INSTANCE;//延遲加載
    private Singleton(){};
    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}

靜態內部類

延遲加載,線程安全。
靜態內部類屬於被動引用,只有在調用時才加載。而JVM的類加載機制會保證只有一個線程成功執行<clinit>方法,初始化類變量。

public class Singleton {
    private Singleton(){}
    private static class SingletonHolder {
        private static Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

枚舉

延遲加載,線程安全。

public class Singleton {
    private Singleton(){}
    
    //靜態枚舉類
    static enum SingletonHolder {
        INSTANCE;
        private Singleton singleton;

        private SingletonHolder() {
            singleton = new Singleton();
        }

        public Singleton getInstance() {
            return singleton;
        }
    }

    //對外暴露的獲取方法
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE.getInstance();
    }
    
}

單例與反射

反射破壞單例:

public class Singleton {
    private Singleton(){}
    private static class SingletonHolder {
        private static Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }


    public static void main(String[] args) throws Exception {
        Class clazz = Singleton.class;
        Constructor constructor = clazz.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        Singleton o1 = (Singleton) constructor.newInstance();
        Singleton o2 = (Singleton) constructor.newInstance();
        System.out.println(o1);
        System.out.println(o2);
        System.out.println(o1 == o2);//false
    }
}

一般而言有兩種解決方案:

一:在私有構造中設置標誌位,進行訪問次數的判斷,限制只能訪問一次構造函數。

public class Singleton {
    private static boolean flag = false;
    private Singleton(){
        if (flag  == false) {
            flag = true;
        } else {
            throw new RuntimeException("反射嘗試破壞...");
        }
    }
    private static class SingletonHolder {
        private static Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }


    public static void main(String[] args) throws Exception {
        Class clazz = Singleton.class;
        Constructor constructor = clazz.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        Singleton o1 = (Singleton) constructor.newInstance();
        Singleton o2 = (Singleton) constructor.newInstance();
        System.out.println(o1);
        System.out.println(o2);
        System.out.println(o1 == o2);
    }
}

在這裏插入圖片描述

二:直接使用枚舉式單例,枚舉沒有無參構造,天然單例防反射。

單例與序列化

序列化單例對象,再反序列化也可以破壞單例模式:

public class Singleton implements Serializable{
    private static boolean flag = false;
    private Singleton(){
        if (flag  == false) {
            flag = true;
        } else {
            throw new RuntimeException("反射嘗試破壞...");
        }
    }
    private static class SingletonHolder {
        private static Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }


    public static void main(String[] args) throws Exception {
        Singleton source = Singleton.getInstance();
        writeObject(source);//序列化
        Singleton target = (Singleton) readObjcet();//反序列化
        System.out.println(source);
        System.out.println(target);
        System.out.println(source == target);
    }

    public static void writeObject(Object o) {
        FileOutputStream fileOut = null;
        ObjectOutputStream out = null;
        try {
            fileOut = new FileOutputStream("test.txt");
            out = new ObjectOutputStream(fileOut);
            out.writeObject(o);
            System.out.println("Serialized data is saved");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fileOut.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    public static Object readObjcet() {
        Object temp = null;
        FileInputStream fileIn = null;
        ObjectInputStream in = null;
        try {
            fileIn = new FileInputStream("test.txt");
            in = new ObjectInputStream(fileIn);
            temp  =  in.readObject();
            System.out.println("Deserialized Object...");
            return temp;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }finally {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                fileIn.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

在這裏插入圖片描述

解決方案:添加readResolve()方法,在方法中返回單例

public class Singleton implements Serializable{
    private static boolean flag = false;
    private Singleton(){
        if (flag  == false) {
            flag = true;
        } else {
            throw new RuntimeException("反射嘗試破壞...");
        }
    }
    private static class SingletonHolder {
        private static Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

    protected Object readResolve() throws ObjectStreamException{
        System.out.println("調用了readReslove...");
        return SingletonHolder.INSTANCE;
    }
}

在這裏插入圖片描述

原理:在ObjectInputStream類的readOrdinaryObject()方法中,對readResolve()方法的存在做了判斷,如果存在,則調用該方法獲取返回值作爲最終返回。

在這裏插入圖片描述

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