2020年5月30日星期六

单例模式,反射破环?

单例模式,反射破环?


饿汉式

// 饿汉式单例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); }}

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


没有评论:

发表评论