设计模式之 反射玩转单例模式、枚举防止反射

8、单例模式

饿汉式单例

饿汉式单例, 在类中直接就实例化了一个对象, 提供对外的方法返回该对象。构造器私有

public class Hungry {
    private Hungry() {
    }

    private final static Hungry HUNGRY = new Hungry();

    public static Hungry getInstance() {
        return HUNGRY;
    }
}

懒汉式

懒汉式单例, 只是简单的申明了一个对象, 在对外的方法中才会实例化改对象。构造器私有,但是在多线程的情况下, 该单例模式会被破坏, 需要对该单例使用synchronized 把该类锁住, 但是这样的单例还是会出现问题的, 因为在实例化对象的时候 new(1、分配内存空间,2、执行构造器, 初始化对象, 3、把对象指向这个内存空间。) 这个关键字并不是一个原子性的操作, 会发生指令重排的现象。在多线程的情况下他是不安全的, 所以我们需要用 volatile 关键字修饰一下, 防止指令重排。

public class LazyMan2 {
    private LazyMan2() {
        System.out.println(Thread.currentThread().getName());
    }

    private volatile static LazyMan2 LAZY_MAN;

    /**
     * 加锁
     * new LazyMan2(); 这个操作不是原子性操作, 可能会发生指令重排
     * <p>
     * 添加 volatile 关键字防止指令重排
     *
     * @return
     */
    public static LazyMan2 getLazyMan() {
        if (LAZY_MAN == null) {
            synchronized (LazyMan2.class) {
                if (LAZY_MAN == null) {
                    LAZY_MAN = new LazyMan2();
                }
            }
        }
        return LAZY_MAN;
    }

    /**
     * 此时的单例还是存在问题的
     * 可以使用反射来破坏单例
     * @param args
     */
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                LazyMan2.getLazyMan();
            }).start();
        }
    }
}

反射破坏

public class LazyMan3 {
    private LazyMan3() {
        synchronized (LazyMan3.class) {
            System.out.println(Thread.currentThread().getName());
            if (lazyMan3 != null) {
                throw new RuntimeException("不要用反射破坏单例模式");
            }
        }
    }

    private volatile static LazyMan3 lazyMan3;

    /**
     * 加锁
     * new LazyMan2(); 这个操作不是原子性操作, 可能会发生指令重排
     * <p>
     * 添加 volatile 关键字防止指令重排
     *
     * @return
     */
    public static LazyMan3 getLazyMan() {
        if (lazyMan3 == null) {
            synchronized (LazyMan3.class) {
                if (lazyMan3 == null) {
                    lazyMan3 = new LazyMan3();
                }
            }
        }
        return lazyMan3;
    }

    /**
     * 此时的单例还是存在问题的
     * 可以使用反射来破坏单例
     *
     * @param args
     */
    public static void main(String[] args) throws Exception {
        // LazyMan3 lazyMan = LazyMan3.getLazyMan();
        /**
         * 因为第一个对象是通过构造器创建的,所以使用反射调用构造器创建第二个对象的时候会出现异常,
         *
         * - 当我们的对象都是通过反射创建的话,就依旧可以破坏单例模式
         * 先得到空参构造器
         *
         * 然后用构造器创建对象
         */
        Constructor<LazyMan3> constructor = LazyMan3.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        LazyMan3 man3 = constructor.newInstance();
        LazyMan3 lazyMan = constructor.newInstance();
        System.out.println(man3);
        System.out.println(lazyMan);

    }
}

使用标志位来防止被反射

public class LazyMan4 {

    private static boolean flag = false;

    private LazyMan4() {
        synchronized (LazyMan4.class) {
            System.out.println(Thread.currentThread().getName());
            if (flag == false) {
                flag = true;
            } else {
                throw new RuntimeException("不要用反射破坏单例模式");
            }
        }
    }

    private volatile static LazyMan4 lazyMan4;

    /**
     * 加锁
     * new LazyMan2(); 这个操作不是原子性操作, 可能会发生指令重排
     * <p>
     * 添加 volatile 关键字防止指令重排
     *
     * @return
     */
    public static LazyMan4 getLazyMan() {
        if (lazyMan4 == null) {
            synchronized (LazyMan4.class) {
                if (lazyMan4 == null) {
                    lazyMan4 = new LazyMan4();
                }
            }
        }
        return lazyMan4;
    }

    /**
     * 此时的单例还是存在问题的
     * 可以使用反射来破坏单例
     *
     * @param args
     */
    public static void main(String[] args) throws Exception {
        // LazyMan3 lazyMan = LazyMan3.getLazyMan();
        /**
         * 因为第一个对象是通过构造器创建的,所以使用反射调用构造器创建第二个对象的时候会出现异常,
         *
         * - 当我们的对象都是通过反射创建的话,就依旧可以破坏单例模式
         *
         * - 此时通过添加标志位来改变这个 问题
         *
         * - 但是此时依旧可以通过反射来修改我们的字段
         * 先得到空参构造器
         *
         * 然后用构造器创建对象
         */

        /**
         * 找到标志位, 直接修改
         */
        Field flag = LazyMan4.class.getDeclaredField("flag");
        flag.setAccessible(true);

        Constructor<LazyMan4> constructor = LazyMan4.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        LazyMan4 man3 = constructor.newInstance();

        /**
         * 在执行第一次后直接修改为 true
         */
        flag.set(man3, false);

        LazyMan4 lazyMan = constructor.newInstance();
        System.out.println(man3);
        System.out.println(lazyMan);

    }
}

使用枚举类

public enum LazyMan5 {
    LazyMan5;
    private static boolean flag = false;

    private LazyMan5() {
        synchronized (LazyMan5.class) {
            System.out.println(Thread.currentThread().getName());
        }
    }

    private volatile static LazyMan5 lazyMan5 = LazyMan5;

    /**
     * 加锁
     * new LazyMan2(); 这个操作不是原子性操作, 可能会发生指令重排
     * <p>
     * 添加 volatile 关键字防止指令重排
     *
     * @return
     */
    public static LazyMan5 getLazyMan() {
        if (lazyMan5 == null) {
            synchronized (LazyMan5.class) {
                if (lazyMan5 == null) {
                    return LazyMan5;
                }
            }
        }
        return lazyMan5;
    }

    /**
     * 此时的单例还是存在问题的
     * 可以使用反射来破坏单例
     *
     * @param args
     */
    public static void main(String[] args) throws Exception {
        // LazyMan3 lazyMan = LazyMan3.getLazyMan();
        /**
         * 因为第一个对象是通过构造器创建的,所以使用反射调用构造器创建第二个对象的时候会出现异常,
         *
         * - 当我们的对象都是通过反射创建的话,就依旧可以破坏单例模式
         *
         * - 此时通过添加标志位来改变这个 问题
         *
         * - 但是此时依旧可以通过反射来修改我们的字段
         *
         * 使用枚举来解决问题, 源码如下
         *
         *         if ((this.clazz.getModifiers() & 16384) != 0) {
         *             throw new IllegalArgumentException("Cannot reflectively create enum objects");
         *         } else {
         *
         *
         * 不能使用枚举来破坏单例
         * Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
         * 先得到空参构造器
         *
         * 然后用构造器创建对象
         */

        /**
         * 找到标志位, 直接修改
         */
        Field flag = LazyMan5.class.getDeclaredField("flag");
        flag.setAccessible(true);

        Constructor<LazyMan5> constructor = LazyMan5.class.getDeclaredConstructor(String.class, int.class);
        constructor.setAccessible(true);
        LazyMan5 man3 = constructor.newInstance(String.class, int.class);

        /**
         * 在执行第一次后直接修改为 true
         */
        flag.set(man3, false);

        LazyMan5 lazyMan = constructor.newInstance(String.class, int.class);
        System.out.println(man3);
        System.out.println(lazyMan);

    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值