为何枚举方式是最好的单例实现方式?
前几种方式实现单例都有如下3个特点:
- 构造方法私有化
- 实例化的变量引用私有化
- 获取实例的方法共有
这种实现方式的问题就在低一点:私有化构造器并不保险。因为它抵御不了反射攻击,比如如下示例代码:
以大家最为常用的饿汉式为例,看我怎么攻击它
public class Singleton implements Serializable { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } } public class Main { public static void main(String[] args) throws Exception { Singleton s = Singleton.getInstance(); // 拿到所有的构造函数,包括非public的 Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(); constructor.setAccessible(true); // 使用空构造函数new一个实例。即使它是private的~~~ Singleton sReflection = constructor.newInstance(); System.out.println(s); //com.fsx.bean.Singleton@1f32e575 System.out.println(sReflection); //com.fsx.bean.Singleton@279f2327 System.out.println(s == sReflection); // false } }
运行输出:
com.fsx.bean.Singleton@1f32e575 com.fsx.bean.Singleton@279f2327 false
通过反射,竟然给所谓的单例创建出了一个新的实例对象。所以这种方式也还是存在不安全因素的。怎么破???如何解决???
其实Joshua Bloch说了:可以在构造函数在被第二次调用的时候抛出异常。具体示例代码,可以参考枚举实现的源码,哈哈。
再看看它的序列化、反序列时会不会有问题。如下:
注意:JDK的序列化、反序列化底层并不是反射~~~
public class Main { public static void main(String[] args) throws Exception { Singleton s = Singleton.getInstance(); byte[] serialize = SerializationUtils.serialize(s); Object deserialize = SerializationUtils.deserialize(serialize); System.out.println(s); System.out.println(deserialize); System.out.println(s == deserialize); } }
运行结果:
com.fsx.bean.Singleton@452b3a41 com.fsx.bean.Singleton@6193b845 false
可以看出,序列化前后两个对象并不相等。所以它序列化也是不安全的
下面看看枚举大法
使用枚举实现单例极其的简单:
public enum EnumSingleton { INSTANCE; }
首先看看是否防御反射攻击:
public class Main { public static void main(String[] args) throws Exception { EnumSingleton s = EnumSingleton.INSTANCE; // 拿到所有的构造函数,包括非public的 Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(); constructor.setAccessible(true); // 使用空构造函数new一个实例。即使它是private的~~~ EnumSingleton sReflection = constructor.newInstance(); System.out.println(s); //com.fsx.bean.Singleton@1f32e575 System.out.println(sReflection); //com.fsx.bean.Singleton@279f2327 System.out.println(s == sReflection); // false } }
结果运行就报错:
Exception in thread "main" java.lang.NoSuchMethodException: com.fsx.bean.EnumSingleton.<init>() at java.lang.Class.getConstructor0(Class.java:3082) at java.lang.Class.getDeclaredConstructor(Class.java:2178) at com.fsx.maintest.Main.main(Main.java:19)