单例模式的安全性

/ 大话设计模式 / 6 条评论 / 371人围观

一个简单的饿汉单例

/**
 * description 普通的饿汉单例模式
 *
 * @author 70KG
 * @date 2018/9/26
 */
public class NormalHungrySingleton {

    private static final NormalHungrySingleton INSTANCE = new NormalHungrySingleton();

    private String name;

    public String getName() {
        return this.name;
    }

    private NormalHungrySingleton() {
        this.name = "70KG";
    }

    public static NormalHungrySingleton getInstance() {
        return INSTANCE;
    }

}

客户端调用

/**
 * description
 *
 * @author 70KG
 * @date 2018/9/26
 */
public class App1 {

    public static void main(String[] args) {

        // 由于构造方法上加了 private 修饰,所以已经不能通过 ‘new’ 来产生实例了
        // NormalHungrySingleton intance = new NormalHungrySingleton();

        NormalHungrySingleton instance1 = NormalHungrySingleton.getInstance();
        NormalHungrySingleton instance2 = NormalHungrySingleton.getInstance();
        System.out.println(instance1 == instance2);
    }

}

结果很显然是true

反射破坏单例

/**
 * description
 *
 * @author 70KG
 * @date 2018/9/26
 */
public class App2 {

    public static void main(String[] args) throws Exception {

        NormalHungrySingleton instance1 = NormalHungrySingleton.getInstance();
        // 通过反射得到其构造方法,并且修改其构造方法的访问权限,并用这个构造方法构造一个对象
        Constructor constructor = NormalHungrySingleton.class.getDeclaredConstructor();
        // 打破封装
        constructor.setAccessible(true);
        NormalHungrySingleton instance2 = (NormalHungrySingleton) constructor.newInstance();
        // 产生了两个实例
        System.out.println(instance1 == instance2); // false
    }

}

结果肯定是false,因为反射破坏了单例。

防止反射破坏

/**
 * description
 *
 * @author 70KG
 * @date 2018/9/26
 */
public enum Singleton {

    INSTANCE;// 这里只有一项

    private String name;

    Singleton() {
        this.name = "70KG";
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }

    public String getName() {
        return this.name;
    }

    static class Test {
        public static void main(String[] args) throws Exception {
            Singleton instance1 = Singleton.getInstance();

            // 通过反射得到其构造方法,并且修改其构造方法的访问权限,并用这个构造方法构造一个对象
            Constructor constructor = Singleton.class.getDeclaredConstructor();
            constructor.setAccessible(true);
            Singleton instance2 = (Singleton) constructor.newInstance();

            // 产生了两个实例
            System.out.println(instance1 == instance2); // false


//            Singleton instance1 = Singleton.getInstance();
//            Singleton instance2 = Singleton.getInstance();
//            System.out.println(instance1 == instance2);
        }
    }

}

会抛出 NoSuchMethodException 异常,因为在代码中是获取不到 enum 类的构造方法的。

Exception in thread "main" java.lang.NoSuchMethodException: com.javadoop.Singleton.<init>()
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.getDeclaredConstructor(Class.java:2178)
    at com.javadoop.singleton.APP.main(APP.java:11)

通过序列化破坏

/**
 * description 通过序列化和反序列化来创建单例对象
 *
 * @author 70KG
 * @date 2018/9/26
 */
public class App3 {

    public static void main(String[] args) throws Exception {
        NormalHungrySingleton instance1 = NormalHungrySingleton.getInstance();

        Constructor constructor = NormalHungrySingleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        NormalHungrySingleton instance2 = (NormalHungrySingleton) constructor.newInstance();

        // instance3 将从 instance1 序列化后,反序列化而来
        NormalHungrySingleton instance3 = null;
        ByteArrayOutputStream bout = null;
        ObjectOutputStream out = null;
        try {
            // 序列化
            bout = new ByteArrayOutputStream();
            out = new ObjectOutputStream(bout);
            out.writeObject(instance1);
            // 反序列化
            ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
            ObjectInputStream in = new ObjectInputStream(bin);
            instance3 = (NormalHungrySingleton) in.readObject();
        } catch (Exception e) {
        } finally {
            // close bout&out
        }

        // 显然,instance3 和 instance1 不是同一个对象了
        System.out.println(instance1 == instance3); // false
    }

}

结果肯定是false了

防止序列化破坏

只需在实体中加入readResolve()方法,因为在反序列化的时候,JVM 会自动调用 readResolve() 这个方法,可以在这个方法中替换掉从流中反序列化回来的对象。

/**
 * description 普通的饿汉单例模式
 *
 * @author 70KG
 * @date 2018/9/26
 */
public class NormalHungrySingleton implements Serializable {

    private static final long serialVersionUID = 3281669744164975370L;

    private static final NormalHungrySingleton INSTANCE = new NormalHungrySingleton();

    private String name;

    public String getName() {
        return this.name;
    }

    private NormalHungrySingleton() {
        this.name = "70KG";
    }

    public static NormalHungrySingleton getInstance() {
        return INSTANCE;
    }

    public Object readResolve() throws ObjectStreamException {
        return INSTANCE;
    }

}
  1. 666666

    回复
  2. 裤衩甩给你

    回复
  3. 6666666

    回复
  4. 123123123

    回复