多种方法破解单例模式(单例模式到枚举的学习)

饿汉式:饿汉式,一开始就把对象创建好,然后返回对象给调用方法的人,缺点浪费空间,优点,一开始用final写死了对象,对象怎么样都不会出现新的。

package single;

public class Hungry {
    private Hungry() {
    }
    private final static Hungry HUNGRY =new Hungry();

    public static Hungry getInstance(){
        return HUNGRY;
    }


    public static void main(String[] args) {
        for (int i = 0; i <10 ; i++) {
            new Thread(()->{
                Hungry.getInstance();
            }).start();
        }
    }
}
    //饿汉式,一开始就把对象创建好,然后返回对象给调用方法的人


懒汉式:等需求的时候再创建,这个比较简单,就是调用getInstance的时候,判断定义里的对象是否为空,不为空就返回一个新对象。优点:需求的时候才创建,节省空间。缺点:多线程下会出现问题。

package single;
public class LazyMan {
    private  static LazyMan lazyMan;

    private LazyMan() {
        System.out.println(Thread.currentThread().getName());
    }
    public static LazyMan getInstance(){
        if (lazyMan==null)
        {
            lazyMan=new LazyMan();
            System.out.println(lazyMan);
        }
        return lazyMan;
    }

    public static void main(String[] args) {
        for (int i = 0; i <10 ; i++) {
            new Thread(()->{
                 LazyMan.getInstance();
            }).start();
        }
    }
}

结果:多线程下出现了这样的结果,说明这并不符合单例这个条件

Thread-0
Thread-1
single.LazyMan@5eefc31e
single.LazyMan@16187bcd


DCL懒汉式:在获取对象的时候,加上判断,如果lazyMan为空,则加上类锁,这样就防止多个线程同时去创建lazyMan。但是还是有缺陷,如果我们此时使用反射获取对象,则会出现问题。

private volatile   static LazyMan lazyMan; 
//由于创造对象是分成了三步所以必须加上volatile来防止指令重排
//1、分配内存空间
//2、执行构造方法
//3、返回对象
//如果执行的123则没问题,但就怕执行132则出错

public static LazyMan getInstance(){
        if (lazyMan==null)
        {   synchronized (LazyMan.class){
            if (lazyMan==null)
            {
                lazyMan=new LazyMan();
                System.out.println(lazyMan);
            }
        }

        }
        return lazyMan;
    }

正常获取:

Thread-0
single.LazyMan@469dad33

使用反射获取:

package single;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class LazyMan {

    private LazyMan() {
        System.out.println(Thread.currentThread().getName());
    }
    private volatile   static LazyMan lazyMan;

    public static LazyMan getInstance(){
        if (lazyMan==null)
        {   synchronized (LazyMan.class){
            if (lazyMan==null)
            {
                lazyMan=new LazyMan();
                System.out.println(lazyMan);
            }
        }

        }
        return lazyMan;
    }

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


        Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true); //取消私有化
        LazyMan lazyMan1 = constructor.newInstance();
        LazyMan lazyMan2 = constructor.newInstance();
        System.out.println(lazyMan1);
        System.out.println(lazyMan2);
    }
}

输出结果:

main
main
single.LazyMan@14ae5a5
single.LazyMan@7f31245a

说明反射的确可以处理这个问题,但是还有办法再防止这种反射,就是设置标志位,但是防止不了反编译:

package single;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class LazyMan {
    private static boolean zsp =false;
    private LazyMan() {
        if (zsp==false)
        {
            zsp=true;
        }
        else
        {
            throw new RuntimeException("不要试图用反射来破坏单例模式");
        }

        System.out.println(Thread.currentThread().getName());
    }
    private volatile   static LazyMan lazyMan;

    public static LazyMan getInstance(){
        if (lazyMan==null)
        {   synchronized (LazyMan.class){
            if (lazyMan==null)
            {
                lazyMan=new LazyMan();
                System.out.println(lazyMan);
            }
        }

        }
        return lazyMan;
    }

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


        Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true); //取消私有化
        LazyMan lazyMan1 = constructor.newInstance();
        LazyMan lazyMan2 = constructor.newInstance();
        System.out.println(lazyMan1);
        System.out.println(lazyMan2);
    }
}

说明的确是可以的,但是无法防止对方反编译去获取我们的标志位从而使用反射改变标志位和反射结果:

main
Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at single.LazyMan.main(LazyMan.java:42)
Caused by: java.lang.RuntimeException: 不要试图用反射来破坏单例模式
	at single.LazyMan.<init>(LazyMan.java:15)
	... 5 more

Process finished with exit code 1

这里我们就需要引入枚举:

package single;



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

class  Test{
    public static void main(String[] args) {
        EnumSingle enumSingle1 =EnumSingle.INSTANCE;
        EnumSingle enumSingle2 =EnumSingle.INSTANCE;
        System.out.println(enumSingle1);
        System.out.println(enumSingle2);
    }
}

输出出来变成:

INSTANCE
INSTANCE

使用反射来破坏,发现错误,构造方法并不是空参:

package single;


import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

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

class  Test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumSingle enumSingle1 =EnumSingle.INSTANCE;
        EnumSingle enumSingle2 =EnumSingle.INSTANCE;
        System.out.println(enumSingle1);
        System.out.println(enumSingle2);
        Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        EnumSingle enumSingle = constructor.newInstance();
        System.out.println(enumSingle);
    }
}

但是报的错误并不是newInstance里的错误Cannot reflectively create enum objects,而是没有空参构造

INSTANCE
INSTANCE
Exception in thread "main" java.lang.NoSuchMethodException: single.EnumSingle.<init>()

使用jad查看后发现:
在这里插入图片描述
并么有空参构造,而是String,int
修改后发现:

        Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);

INSTANCE
INSTANCE
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
	at single.Test.main(EnumSingle.java:22)

最后证明了枚举可以很好的解决反射去调用单例模式的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值