设计模式——单例模式与反射破坏单例模式

单例模式:保证一个类只有一个实例 ,并且提供一个访问该实例的全局访问点
常见的单例模式形式
1.饿汉式单例模式

为什么叫饿汉式呢?就比如你饿得很快,手边必须事先放着吃的东西才可以,有所准备,不管我饿不饿反正必须得有!所以饿汉式单例模式就是不管程序是否需要这个类的实例,总是在类加载的时候就创建好实例。请看代码, 通过私有构造器,在类的内部创建一个final static实例,这样当该类被初始化的时候,实例就会被创建好并且唯一,通过get方法这一访问点就可以访问该类的实例了。可是你准备的吃的放在那里不吃,不就占你家的空间吗?所以饿汉式的一个缺点比如你在代码里声明的一些数组,你没使用就白白浪费着空间~

//饿汉式单例
public class HungryPattern {

    //public byte[] bytes = new byte[1024];
    //私有化构造器
    private HungryPattern(){
    }
    public void hello(){
        System.out.println("Hello");
    }
    //类初始化的时候就立即加载该类的实例,不能被继承
    private final static HungryPattern instance = new HungryPattern();
    //提供获取该对象的方法 没有synchronized 效率高!
    public static HungryPattern getInstance(){
        return instance;
    }
}

//缺点 代码一运行,bytes已经占用了空间,如果长时间不调用这个类,就会浪费空间

通过代码我们知道,饿汉式单例模式线程安全(不排除反射),效率较高(没有加锁),但是不能延时加载

2.懒汉式单例模式

懒汉总是不愿意动手,除非自己饿的实在受不了了,才去找吃的。所以懒汉式单例模式就是等我真正需要的时候我才创建实例,考虑到线程安全的问题,我们需要在get方法加一个synchronized关键字,这样我们就能保证多线程的情况下仍然只有一个实例生成了~

//懒汉式单例
public class LazyPattern {
    //私有化构造器
    private LazyPattern(){
    }
    //类初始化的时候不立即加载该对象
    private static LazyPattern instance;
    //提供获取该对象的方法 并发情况下会生成多个实例所以加synchronized
    //有synchronized 效率较低
    public static synchronized LazyPattern getInstance(){
        if(instance == null){
            instance = new LazyPattern();
        }
        return instance;
    }
}

通过代码我们知道,懒汉式单例模式线程安全(不排除反射),由于加了锁所以效率不高,但可以延时加载

3.DCL(Double Checked Locking)懒汉式单例模式

看英文就知道我们需要上两层锁,为什么需要两层锁呢?我个人是这样理解的,比如有两个线程小白和小黑,其中小白先进来发现instance为空,于是小白就走到了创建实例这一步,但是小白还没创建完呢,此时小黑突然也进来了,小黑发现instance也为空,于是小黑也往创建实例这一步走了,这样可能就创建了两个实例,所以我们第一步就要锁着这个类,当小白进来的时候,不让其他的线程再进来,等我创建完成后其他的线程你才能打这个类的主意,这样其他线程就不会发现没有instance了~这里我们还需要使用volatile关键字避免指令重排,为什么呢?由于我们new对象的时候,不是一蹴而就的,它不是一个原子性操作,分为几步比如1.分配内存空间2.执行构造方法,初始化对象3.把这个对象指向这个空间。线程正常走123没有问题,但由于指令重排,线程可能会走132这一条路,比如小白走了132,当小白走到3的时候,这个空间已经不为空了,但是还没初始化对象呢。此时小黑也走到了3,但小黑看见空间不为空,认为对象已经初始化好了,就傻傻的走return了,这样就出现了问题,所以我们要加volatile关键字避免指令重排。

//DCL懒汉式
public class LazyDCL {
    //私有化构造器
    private LazyDCL(){
    }
    //类初始化的时候不立即加载该对象,volatile避免指令重排
    private volatile static LazyDCL instance;

    //双重检测锁模式下的懒汉式单例 DCL懒汉式
    public static LazyDCL getInstance(){
        if(instance == null){
            synchronized (LazyDCL.class){
                if(instance==null){
                    instance = new LazyDCL(); //不是一个原子性操作
                }
            }
        }
        return instance;
    }
}

似乎这种方法更好一点,但很多人说不建议使用,具体我也没接触过,毕竟就是个小白。

4.静态内部类实现单例模式
//静态内部类实现单例模式
public class Holder {
    private Holder(){
    }

    public static Holder getInstance(){
        return InnerClass.HOLDER;
    }

    public static class InnerClass{
        private static final Holder HOLDER = new Holder();
    }
}
5.枚举单例
//枚举本身是一个类
public enum EnumSingle {
    INSTANCE;

    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

class Test{
    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        EnumSingle enumSingle = EnumSingle.INSTANCE;
        EnumSingle enumSingle1 = EnumSingle.INSTANCE; //可以看到两者相等,枚举确实是单例的
        //破坏枚举的单例,反射无法破坏枚举的单例
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle enumSingle2 = declaredConstructor.newInstance();

        System.out.println(enumSingle);
        System.out.println(enumSingle1);
        System.out.println(enumSingle2);

    }
}
利用反射破坏单例模式
1.

我们都知道反射这个魔鬼,通过反射可以破坏单例模式,怎么破坏呢?我们可以通过反射获得私有的构造器,通过把这个构造器的setAccessible属性设置为true,这样直接无视了构造器的私有性,我们先通过正常的getInstance()方法创建一个实例,再通过反射得到的构造器创建一个实例,经过测试,发现生成的实例确实不是同一个。怎么解决呢?在构造器里加锁,当第二次创建实例的时候,instance已经不为空,知道你是搞破坏的,直接给你个异常。

/**
 * 使用反射破坏
 * 破坏操作:无视私有构造器创建实例
 * 解决办法:在私有构造器里上锁解决
 */

public class Crackdemo01 {
    //私有化构造器
    private Crackdemo01(){
        synchronized (Crackdemo01.class){
            if(instance!=null){
                throw new RuntimeException("不要用反射破坏单例模式");
            }
        }
    }
    //类初始化的时候不立即加载该对象,volatile避免指令重排
    private volatile static Crackdemo01 instance;

    //双重检测锁模式下的懒汉式单例 DCL懒汉式
    public static Crackdemo01 getInstance(){
        if(instance == null){
            synchronized (Crackdemo01.class){
                if(instance==null){
                    instance = new Crackdemo01();            
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Crackdemo01 instance = Crackdemo01.getInstance();
        Constructor<Crackdemo01> declaredConstructor = Crackdemo01.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true); //无视私有的构造器
        Crackdemo01 instance2 = declaredConstructor.newInstance();

        System.out.println(instance);
        System.out.println(instance2);

    }
}
2.

在上述破坏方式1中,当我们不使用类的getInstance()的时候,而是通过反射获得的构造器创建两个或者多个实例时,此时instance将会一直为null,所以在构造器里加的是否为空判断条件失效了,单例模式又被破坏了,怎么解决呢?设置一个标志位来判断,不管你怎么创建实例,只要第一次创建我会把标志位设置成已创建,当再想创建的时候,代码发现标志位已经是创建标志位,直接抛出一个异常。

/**
 * 使用反射破解
 * 1.两个实例都无视私有构造器创建实例 单例模式又被破坏
 * 2.通过红绿灯标志位方法解决
 */

public class Crackdemo02 {
    private static boolean safe = false;
    //私有化构造器
    private Crackdemo02(){
        synchronized (Crackdemo02.class){
            if(safe == false){
                safe = true;
            }else {
                throw new RuntimeException("不要用反射破坏异常");
            }
        }
    }
    //类初始化的时候不立即加载该对象,volatile避免指令重排
    private volatile static Crackdemo02 instance;

    //双重检测锁模式下的懒汉式单例 DCL懒汉式
    public static Crackdemo02 getInstance(){
        if(instance == null){
            synchronized (Crackdemo02.class){
                if(instance==null){
                    instance = new Crackdemo02(); //不是一个原子性操作
                 
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<Crackdemo02> declaredConstructor = Crackdemo02.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true); //无视私有的构造器
        Crackdemo02 instance = declaredConstructor.newInstance();
        Crackdemo02 instance2 = declaredConstructor.newInstance();

        System.out.println(instance);
        System.out.println(instance2);

    }
}
3.

你挡住了反射两次,反射又不服了,它通过反射获取到了标志位的字段,和构造方法一样,将标志位字段的setAccessible属性设置为true,然后创建一个实例后,就把标志位改成初始标志位,也就是我创建一个将标志位改为未创建,创建一个将标志位改为未创建…这样单例模式又被破坏了,这样可以通过一些加密来保护这个标志位字段。

/**
 * 使用反射破解
 * 1.两个实例都无视私有构造器创建实例,单例模式又被破坏
 * 2.通过红绿灯标志位方法解决
 * 3.通过反射拿到标志位字段
 * 4.通过反射创建一个实例后,修改字段为初始字段,发现单例又被破坏
 */

public class Crackdemo03 {
    private static boolean safe = false;
    //私有化构造器
    private Crackdemo03(){
        synchronized (Crackdemo03.class){
            if(safe == false){
                safe = true;
            }else {
                throw new RuntimeException("不要用反射破坏异常");
            }
        }
    }
    //类初始化的时候不立即加载该对象,volatile避免指令重排
    private volatile static Crackdemo03 instance;

    //双重检测锁模式下的懒汉式单例 DCL懒汉式
    public static Crackdemo03 getInstance(){
        if(instance == null){
            synchronized (Crackdemo03.class){
                if(instance==null){
                    instance = new Crackdemo03(); //不是一个原子性操作
                 
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {

        Field safe = Crackdemo03.class.getDeclaredField("safe");
        safe.setAccessible(true);

        Constructor<Crackdemo03> declaredConstructor = Crackdemo03.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true); //无视私有的构造器
        Crackdemo03 instance = declaredConstructor.newInstance();

        safe.set(instance,false);

        Crackdemo03 instance2 = declaredConstructor.newInstance();

        System.out.println(instance);
        System.out.println(instance2);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jumanji_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值