单例模式,反射破环?

Posted yslss

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式,反射破环?相关的知识,希望对你有一定的参考价值。

饿汉式

// 饿汉式单例
public class Hungry {
    
    //构造器私有
    private Hungry(){

    }
    // 一上来就把这个类加载了
    private final static  Hungry HUNGRY = new Hungry();

    public static Hungry getInstance(){
        return HUNGRY;
    }
}
// 饿汉式单例
public class Hungry {
    // 这4组数据非常耗内存资源,饿汉式一上来就把所有的内存里面的东西全部加载进来了,就存在这个空间
    // 但这个空间现在是没有使用的,可能会造成浪费空间
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];
    private byte[] data3 = new byte[1024*1024];
    private byte[] data4 = new byte[1024*1024];
    
    //构造器私有
    private Hungry(){

    }
    // 一上来就把这个类加载了
    private final static  Hungry HUNGRY = new Hungry();

    public static Hungry getInstance(){
        return HUNGRY;
    }

}

饿汉式单例可能会造成浪费空间,所以想要用的时候再去创建这个对象,平时就先放在这个地方,于是就出现了懒汉式!

懒汉式

// 懒汉式单例
public class LazyMan {
	
   // 构造器私有
    private LazyMan(){
       
    }

    private  static LazyMan lazyMan;

   
    public static LazyMan getInstance(){
       
        if (lazyMan==null){
            lazyMan = new LazyMan(); 
        }
        return lazyMan;
    }
}

它是有问题的,单线程下确实单例ok,多线程并发就会出现问题!

测试

// 懒汉式单例
public class LazyMan {
	
   // 构造器私有
    private LazyMan(){
       System.out.println(Thread.currentThread().getName()+":: ok");
    }

    private  static LazyMan lazyMan;

   
    public static LazyMan getInstance(){
       
        if (lazyMan==null){
            lazyMan = new LazyMan(); 
        }
        return lazyMan;
    }
    
    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LazyMan.getInstance();
            }).start();
        }
    }
}

技术图片

技术图片

发现单例有问题,每次结果可能都不一样!

解决

// 懒汉式单例
public class LazyMan {


    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+":: ok");
    }

    private  static LazyMan lazyMan;

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

     public static void main(String[] args) {

         for (int i = 0; i < 10 ; i++) {
             new Thread(()->{
                LazyMan.getInstance();
             }).start();
         }
     }
}

技术图片

但在极端情况下还是可能出现问题

技术图片

经历三个步骤:

1、 分配内存空间

2、 执行构造方法,初始化对象

3、 把这个对象指向这个空间

有可能会发生指令重排的操作!

比如,期望它执行 123 ,但是它真实可能执行132,比如第一个A线程过来执行了132,先分配空间再吧这个空间占用了,占用之后再去执行构造方法,如果现在突然来了个B线程,由于A已经指向这个空间了,它会以为这个 lazyMan 不等于 null ,直接return ,此时lazyMan还没有完成构造,所有必须避免这个问题!

必须加上volatile

// 懒汉式单例
public class LazyMan {


    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+":: ok");
    }
	// 避免指令重排
    private volatile static LazyMan lazyMan;

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

     public static void main(String[] args) {

         for (int i = 0; i < 10 ; i++) {
             new Thread(()->{
                LazyMan.getInstance();
             }).start();
         }
     }
}

静态内部类

// 静态内部类
public class Holder {
    private Holder(){

    }
    public static Holder getInstance(){
        return InnerClass.HOLDER;
    }
    public static  class InnerClass{
        private static final Holder HOLDER = new Holder();
    }
}

也是单例模式的一种,不安全!

单例不安全 反射

// 懒汉式单例
public class LazyMan {


    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+":: ok");
    }

    private volatile static LazyMan lazyMan;

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


    //反射
     public static void main(String[] args) throws Exception {
         LazyMan instance1 = LazyMan.getInstance();
         Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
         declaredConstructor.setAccessible(true); // 无视了私有的构造器
         // 通过反射创建对象
         LazyMan instance2 = declaredConstructor.newInstance();

         System.out.println(instance1);
         System.out.println(instance2);
     }
}

技术图片

结论:反射可以破坏这种单例

解决

// 懒汉式单例
public class LazyMan {

    private LazyMan(){
        synchronized (LazyMan.class){
            if (lazyMan!=null){
                throw new RuntimeException("不要试图使用反射破环 异常");
            }
        }
        System.out.println(Thread.currentThread().getName()+":: ok");
    }

    private volatile static LazyMan lazyMan;

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

    //反射
     public static void main(String[] args) throws Exception {
         LazyMan instance1 = LazyMan.getInstance();
         Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
         declaredConstructor.setAccessible(true); // 无视了私有的构造器
         // 通过反射创建对象
         LazyMan instance2 = declaredConstructor.newInstance();

         System.out.println(instance1);
         System.out.println(instance2);
     }
}

技术图片

但是如果都用反射创建对象的情况下,还是会破环单例!

测试

技术图片

解决

// 懒汉式单例
public class LazyMan {
      // 标志位
    private static boolean abc = false;

    private LazyMan(){
        synchronized (LazyMan.class){
            if (abc==false){
                abc=true;
            }else {
                throw new RuntimeException("不要试图使用反射破环 异常");
            }
          
        }
        System.out.println(Thread.currentThread().getName()+":: ok");
    }
    private volatile static LazyMan lazyMan;

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

    //反射
    public static void main(String[] args) throws Exception {
        //LazyMan instance1 = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true); // 无视了私有的构造器
        // 通过反射创建对象
        LazyMan instance2 = declaredConstructor.newInstance();
        LazyMan instance1 = declaredConstructor.newInstance();

        System.out.println(instance1);
        System.out.println(instance2);
    }
}

技术图片

但是如果被人知道 abc这个变量,也可以破环!

技术图片

单例又被破环了!

看一下源码

技术图片

它说不能使用反射破环枚举,枚举是jdk1.5出现的,自带单例模式!

测试,写一个枚举类

// enum 本身就是一个class类
public enum EnumSingle {

    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

查看它的源码

技术图片

试图破环!

// enum 本身就是一个class类
public enum EnumSingle {

    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}
class Test{
    public static void main(String[] args) throws Exception {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

技术图片

它竟然说我现在的这个枚举类中没有空参构造器!

然后就去源码里分析!

技术图片

找到这个class文件!利用javap反编译一下!

技术图片

发现这个也显示有一个空参构造,证明这个也不对,用第三方的工具查看!

技术图片

利用它再吧class文件生成java文件!

技术图片

技术图片

打开这个java文件

技术图片

证明是idea和源码骗了我!

再次尝试破环!

// enum 本身就是一个class类
public enum EnumSingle {

    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}
class Test{
    public static void main(String[] args) throws Exception {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

技术图片

结论:反射无法破环枚举类!

以上是关于单例模式,反射破环?的主要内容,如果未能解决你的问题,请参考以下文章

详解单例模式

详解单例模式

单例模式-反射攻击的解决方案及原理分析

单例模式--反射--防止序列化破坏单例模式

单例模式反射序列化漏洞及解决方案!

Python入门自学进阶——7--类与对象-异常反射单例模式