[TOC]

1. 单例是什么?

单例模式:是一种创立型设计模式,目标是保障全局一个类只有一个实例对象,分为懒汉式和饿汉式。所谓懒汉式,相似于懒加载,须要的时候才会触发初始化实例对象。而饿汉式正好相同,我的项目启动,类加载的时候,就会创立初始化单例对象。

后面说过单例模式以及如何毁坏单例模式,咱们个别状况尽可能阻止单例模式被毁坏,于是各种序列化,反射,以及克隆的伎俩,咱们都须要思考进来,最终的代码如下:

import java.io.Serializable;public class Singleton implements Serializable {    private static int num = 0;      // valitile禁止指令重排    private volatile static Singleton singleton;      // 禁止屡次反射调用结构器    private Singleton() {        synchronized (Singleton.class) {            if (num == 0) {                num++;            } else {                throw new RuntimeException("Don't use this method");            }        }    }    public static Singleton getSingleton() {        if (singleton == null) {            synchronized (Singleton.class) {                if (singleton == null) {                    singleton = new Singleton();                }            }        }        return singleton;    }      // 禁止序列化的时候,从新生成对象    private Object readResolve() {        return singleton;    }}

后面提过毁坏序列化的四种形式:

  • 没有将结构器私有化,能够间接调用。
  • 反射调用结构器
  • 实现了cloneable接口
  • 序列化与反序列化

2. 枚举的单例能够被毁坏么?

然而忽然想到一个问题,个别都说枚举的形式实现单例比拟好,较为举荐。真的是这样么?这样真的是平安的么?

那咱们就试试,看看各种伎俩,能不能毁坏它的单例。首先咱们来写一个单例枚举类:

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

在命令行执行以下的命令看下面的枚举类编译之后到底是什么货色?

javac SingletonEnum.javajavap SingletonEnum

public final class singleton.SingletonEnum extends java.lang.Enum<singleton.SingletonEnum> {  public static final singleton.SingletonEnum INSTANCE;  public static singleton.SingletonEnum[] values();  public static singleton.SingletonEnum valueOf(java.lang.String);  public singleton.SingletonEnum getInstance();  static {};}

能够看出,实际上,编译后的代码是继承于Enum类的,并且是泛型。用final润饰,其实也是类,那就是不能够被继承起因。而且INSTANCE也是final润饰的,也是不可变的。然而这样看,下面的都是public办法。那构造方法呢?没有被重写成为private么?

要是没有重写的话,那就很容易毁坏单例啊!咱们应用javap -p SingletonEnum看看后果:

<img src="https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20201213225659.png" style="zoom:50%;" />

能够看出的确构造函数曾经被私有化,那么内部就不能间接调用到构造方法了。那其余办法呢?咱们试试喷射调用结构器:

import java.io.*;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;public class SingletonTests {    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {        SingletonEnum singleton1 = SingletonEnum.INSTANCE;        SingletonEnum singleton2 = SingletonEnum.INSTANCE;        System.out.println(singleton1.hashCode());        System.out.println(singleton2.hashCode());        Constructor<SingletonEnum> constructor = null;        constructor = SingletonEnum.class.getDeclaredConstructor();        constructor.setAccessible(true);        SingletonEnum singleton3 = constructor.newInstance();        System.out.println(singleton1 == singleton3);    }}

执行后果如下:

692404036692404036Exception in thread "main" java.lang.NoSuchMethodException: singleton.SingletonEnum.<init>()    at java.lang.Class.getConstructor0(Class.java:3082)    at java.lang.Class.getDeclaredConstructor(Class.java:2178)    at singleton.SingletonTests.main(SingletonTests.java:15)

咦,怎么回事?反射失败了???

<img src="https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20201213231748.png" style="zoom:50%;" />

看起来报错是getDeclaredConstructor()失败了,那咱们看看到底有哪些结构器:

    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {        Constructor<SingletonEnum>[] constructor = null;        constructor = (Constructor<SingletonEnum>[]) SingletonEnum.class.getDeclaredConstructors();        for(Constructor<SingletonEnum> singletonEnumConstructor:constructor){            System.out.println(singletonEnumConstructor);        }    }

执行后果如下,发现只有一个结构器,外面参数是Stringint,所以啊,反射调用无参数结构器必定也是如此。

private singleton.SingletonEnum(java.lang.String,int)

毕竟它是继承于Enum的,那我猜测它大略也只有这个办法,验证以下,关上源码:

public abstract class Enum<E extends Enum<E>>        implements Comparable<E>, Serializable {      private final String name;    public final String name() {        return name;    }    private final int ordinal;    public final int ordinal() {        return ordinal;    }    protected Enum(String name, int ordinal) {        this.name = name;        this.ordinal = ordinal;    }

能够看出,这外面只有两个属性:nameordinal,结构器被重写了,正是Stringint,验证了咱们的猜测,也就是没有方法应用无参数结构器来结构出毁坏单例的对象。那要是咱们应用有参数结构呢?试试!!!

import java.io.*;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;public class SingletonTests {    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {        SingletonEnum singleton1 = SingletonEnum.INSTANCE;        SingletonEnum singleton2 = SingletonEnum.INSTANCE;        System.out.println(singleton1.hashCode());        System.out.println(singleton2.hashCode());        Constructor<SingletonEnum> constructor = null;        constructor = SingletonEnum.class.getDeclaredConstructor(String.class,int.class);//其父类的结构器        constructor.setAccessible(true);        SingletonEnum singleton3 = constructor.newInstance("INSTANCE",0);        System.out.println(singleton1 == singleton3);    }}

后果呢?还是一样的报错,这是什么东东?

692404036692404036Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects    at java.lang.reflect.Constructor.newInstance(Constructor.java:417)    at singleton.SingletonTests.main(SingletonTests.java:18)

看起来意思是不能反射创立enum对象,啥?这谬误一看,就是Constructor.newInstance()417行抛出来的,咱们看看:

    @CallerSensitive    public T newInstance(Object ... initargs)        throws InstantiationException, IllegalAccessException,               IllegalArgumentException, InvocationTargetException    {        if (!override) {            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {                Class<?> caller = Reflection.getCallerClass();                checkAccess(caller, clazz, null, modifiers);            }        }        // 限度枚举类型        if ((clazz.getModifiers() & Modifier.ENUM) != 0)            throw new IllegalArgumentException("Cannot reflectively create enum objects");        ConstructorAccessor ca = constructorAccessor;   // read volatile        if (ca == null) {            ca = acquireConstructorAccessor();        }        @SuppressWarnings("unchecked")        T inst = (T) ca.newInstance(initargs);        return inst;    }

原来反射的源代码中,枚举类型的曾经被限度了,一旦调用就会抛出异样,那这条路走不通了,也就证实了反射无奈毁坏枚举的单例。new对象更是行不通了。

clone呢?关上Enum的源码咱们外面就断了这个念头,这外面的clone()办法,曾经被final润饰了,不能被子类重写,一调用就抛出异样。所以clone这条路也不可能毁坏枚举的单例模式。

    protected final Object clone() throws CloneNotSupportedException {        throw new CloneNotSupportedException();    }

那序列化呢?如果咱们序列化之后,再反序列化,会呈现什么状况?

import java.io.*;import java.lang.reflect.InvocationTargetException;public class SingletonTests {    public static void main(String[] args) throws Exception, InvocationTargetException, InstantiationException, NoSuchMethodException {        SingletonEnum singleton1 = SingletonEnum.getInstance();        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("file"));        objectOutputStream.writeObject(singleton1);        File file = new File("file");        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));        SingletonEnum singleton2 = (SingletonEnum) objectInputStream.readObject();        System.out.println(singleton1.hashCode());        System.out.println(singleton2.hashCode());    }}

下面的代码执行之后,后果如下:

16276740701627674070

阐明序列化反序列化回来之后,其实是同一个对象!!!所以无奈毁坏单例模式。为什么呢?咱们来剖析一下源码!!!

先看看序列化的时候,实际上调用的是ObjectOutputStream.writeObject(Object obj)

writerObject()Object obj办法外面调用了writeObject0(obj,false),writeObject0(obj,false)外面看到枚举类型的序列化写入:

writeEnum(Enum<?>)外面是怎么序列化的呢?

    private void writeEnum(Enum<?> en,                           ObjectStreamClass desc,                           boolean unshared)        throws IOException    {          // 标识是枚举类型        bout.writeByte(TC_ENUM);        ObjectStreamClass sdesc = desc.getSuperDesc();          // 类型形容        writeClassDesc((sdesc.forClass() == Enum.class) ? desc : sdesc, false);        handles.assign(unshared ? null : en);          // 将名字写入name()        writeString(en.name(), false);    }

看起来序列化的时候,是用名字写入序列化流中,那反序列化的时候呢?是怎么操作的呢?

    public final Object readObject()        throws IOException, ClassNotFoundException {        return readObject(Object.class);    }

外面调用的是另外一个readObject()办法,readObject()办法其实是调用了readObject0(type,false)

看到反序列化的时候,枚举类型的时候,是怎么实现的呢?外面有一个readEnum():

咱们来看看readEnum(),外面其实外面是先读取了名字name,再通过名字Enum.valueOf()获取枚举。

所以下面没有应用反射,还是获取了之前的对象,综上所述,枚举的序列化和反序列化并不会影响单例模式。

3. 总结一下

通过下面一顿剖析,枚举不能够间接调用构造函数,不能够反射毁坏单例模式,因为外部实现阻止了,实现clone接口也不能够,这个办法曾经设置为final。序列化和反序列化的时候,外部没有应用反射去实现,而是查找之前的对象,间接返回,所以还是同一个对象。

这样一来,怪不得《effective java》外面举荐这个写法,既简洁,还可能避免各种毁坏,还有不必的理由么?

【作者简介】
秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使迟缓,驰而不息。这个世界心愿所有都很快,更快,然而我心愿本人能走好每一步,写好每一篇文章,期待和你们一起交换。

此文章仅代表本人(本菜鸟)学习积攒记录,或者学习笔记,如有侵权,请分割作者核实删除。人无完人,文章也一样,文笔稚嫩,在下不才,勿喷,如果有谬误之处,还望指出,感激不尽~