单例模式:是一种建立型设计模式,目的是保证全局一个类只有一个实例对象,分为懒汉式和饿汉式。所谓懒汉式,相似于懒加载,须要的时候才会触发初始化实例对象。而饿汉式正好相反,项目启动,类加载的时候,就会建立初始化单例对象。java
前面说过单例模式以及如何破坏单例模式,咱们通常状况尽量阻止单例模式被破坏,因而各类序列化,反射,以及克隆的手段,咱们都须要考虑进来,最终的代码以下:设计模式
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
接口可是忽然想到一个问题,通常都说枚举的方式实现单例比较好,较为推荐。真的是这样么?这样真的是安全的么?ide
那咱们就试试,看看各类手段,能不能破坏它的单例。首先咱们来写一个单例枚举类:函数
public enum SingletonEnum { INSTANCE; public SingletonEnum getInstance(){ return INSTANCE; } }
在命令行执行如下的命令看上面的枚举类编译以后究竟是什么东西?学习
javac SingletonEnum.java javap 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
么?ui
要是没有重写的话,那就很容易破坏单例啊!咱们使用javap -p SingletonEnum
看看结果:this
能够看出确实构造函数已经被私有化,那么外部就不能直接调用到构造方法了。那其余方法呢?咱们试试放射调用构造器:命令行
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); } }
执行结果以下:设计
692404036 692404036 Exception 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)
咦,怎么回事?反射失败了???
看起来报错是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); } }
执行结果以下,发现只有一个构造器,里面参数是String
和int
,因此啊,反射调用无参数构造器确定也是如此。
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; }
能够看出,这里面只有两个属性:name
和ordinal
,构造器被重写了,正是String
和int
,验证了咱们的猜测,也就是没有办法使用无参数构造器来构造出破坏单例的对象。那要是咱们使用有参数构造呢?试试!!!
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); } }
结果呢?仍是同样的报错,这是什么东东?
692404036 692404036 Exception 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()); } }
上面的代码执行以后,结果以下:
1627674070 1627674070
说明序列化反序列化回来以后,实际上是同一个对象!!!因此没法破坏单例模式。为何呢?咱们来分析一下源码!!!
先看看序列化的时候,实际上调用的是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()
获取枚举。
因此上面没有使用反射,仍是获取了以前的对象,综上所述,枚举的序列化和反序列化并不会影响单例模式。
通过上面一顿分析,枚举不能够直接调用构造函数,不能够反射破坏单例模式,由于内部实现阻止了,实现clone
接口也不能够,这个方法已经设置为final
。序列化和反序列化的时候,内部没有使用反射去实现,而是查找以前的对象,直接返回,因此仍是同一个对象。
这样一来,怪不得《effective java》里面推荐这个写法,既简洁,还可以防止各类破坏,还有不用的理由么?
【做者简介】:
秦怀,公众号【秦怀杂货店】做者,技术之路不在一时,山高水长,纵使缓慢,驰而不息。这个世界但愿一切都很快,更快,可是我但愿本身能走好每一步,写好每一篇文章,期待和大家一块儿交流。
此文章仅表明本身(本菜鸟)学习积累记录,或者学习笔记,若有侵权,请联系做者核实删除。人无完人,文章也同样,文笔稚嫩,在下不才,勿喷,若是有错误之处,还望指出,感激涕零~