25

单例模式,反射破环?

 3 years ago
source link: http://www.cnblogs.com/yslss/p/12990783.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

饿汉式

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

vimmQ3Q.png!web

VNzAbmu.png!web

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

解决

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

UZ3AZf7.png!web

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

emE7Jbb.png!web

经历三个步骤:

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);
     }
}

AZ3Mn2F.png!web

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

解决

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

Nf6bmyr.png!web

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

测试

7Jvqimi.png!web

解决

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

B7Fzmef.png!web

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

ny226jb.png!web

单例又被破环了!

看一下源码

EJRz6f2.png!web

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

测试,写一个枚举类

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

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

查看它的源码

Af6bMbN.png!web

试图破环!

// 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);
    }
}

uMrmArI.png!web

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

然后就去源码里分析!

RnIvuqr.png!web

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

NRfa2qv.png!web

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

6vQNZfr.png!web

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

iYV7rin.png!web

yy6ruei.png!web

打开这个java文件

YreQNbq.png!web

证明是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);
    }
}

ANFfqqV.png!web

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK