单例模式及枚举类

328 阅读3分钟

前言:

        准备秋招找工作,应该每个人都会写单例模式吧~ 刚才复习这块的时候,知道枚举类和单例模式也有关,之前只知道枚举类型很强大,整理一下吧~

1.单例模式

懒汉式,饿汉式,静态内部类式,还有枚举类方式(代码就不放了,掘金网这个代码模块每次粘贴上来变成一行,不知道什么原因)

1.为什么要双重检查?(比较口语化哈~)

外面那个if( == null ) 去掉可不可以? 不可以! 如果去掉的话,每次都要加锁去判断,很耗时 ,只有在null的时候去加锁; 那里面那个判断null呢?里面的判断null是外部可能同时进来两个线程,不判断的话 还是解决不了问题

2.那为什么要加volatile呢?

防止指令重排, 新建对象的过程:先分配内存,然后初始化对象,把引用指向对象地址空间,CPU因为要优化一些过程,指令可能重排,这个时候可能会把没有初始化的对象 拿出去用,就会出现问题! 因此要加volatile

**我们知道的除了枚举类以外,其余的单例模式基本都解决不了安全问题,不管用什么方法,都会有漏洞,那怎么解决呢? **

2.枚举

反射和序列化是可以破坏之前的那几种单例模式的,不管怎么样都是可以破坏上面写的几种单例模式的

枚举本身就是一个class类

用枚举来解决,newinstance里面源码标明 如果类型是一个枚举类型 就不能使用反射来破坏枚举类,自带单例模式,还可以防止序列化!

枚举类里面没有空参构造器,只有有参构造! 因此在反射破坏的时候,要传入参数,才会报反射不能破坏枚举类的错误;

哪怕编译以后去看编译文件,会显示里面有无参构造,但是编译器骗了我们?其实里面的是有参构造~ 所以也会报错; 哪怕加入有参构造 也会出问题!

如果类型是一个枚举类型 就不能使用反射来破坏枚举类,自带单例模式;

1.枚举单例的防御机制

对反射的防御

我们直接将上述reflectionAttack()方法中的类名改成EnumSingleton并执行,会发现报如下异常:

Exception in thread "main" java.lang.NoSuchMethodException: me.lmagics.singleton.EnumSingleton.<init>()
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.getDeclaredConstructor(Class.java:2178)
    at me.lmagics.singleton.SingletonAttack.reflectionAttack(SingletonAttack.java:35)
    at me.lmagics.singleton.SingletonAttack.main(SingletonAttack.java:19)

这是因为所有Java枚举都隐式继承自Enum抽象类,而Enum抽象类根本没有无参构造方法,只有如下一个构造方法:

    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }

那么我们就改成获取这个有参构造方法,即:
Constructor constructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
结果还是会抛出异常:

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
    at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
    at me.lmagics.singleton.SingletonAttack.reflectionAttack(SingletonAttack.java:38)
    at me.lmagics.singleton.SingletonAttack.main(SingletonAttack.java:19)

来到Constructor.newInstance()方法中,有如下语句:

    if ((clazz.getModifiers() & Modifier.ENUM) != 0)
        throw new IllegalArgumentException("Cannot reflectively create enum objects");

可见,JDK反射机制内部完全禁止了用反射创建枚举实例的可能性。

2.对序列化的防御

如果将serializationAttack()方法中的攻击目标换成EnumSingleton,那么我们就会发现s1和s2实际上是同一个实例,最终会打印出true。这是因为ObjectInputStream类中,对枚举类型有一个专门的readEnum()方法来处理,其简要流程如下:

  • 通过类描述符取得枚举单例的类型EnumSingleton;
  • 取得枚举单例中的枚举值的名字(这里是INSTANCE);
  • 调用Enum.valueOf()方法,根据枚举类型和枚举值的名字,获得最终的单例。

这种处理方法与readResolve()方法大同小异,都是以绕过反射直接获取单例为目标。不同的是,枚举对序列化的防御仍然是JDK内部实现的。

参考:1.www.jianshu.com/p/d9d9dcf23…

2.bilibli上的狂神说java之单例模式(有兴趣的可以搜下哈~讲的很好)