枚举实现单例是最安全的单例实现方式
实现代码
准备枚举类
/**
* 注册式单例第一种:枚举式单例
*/
public enum EunmSingleton {
INSTANCE;
private Object data;
public static EunmSingleton getInstance(){return INSTANCE;}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
1.尝试通过反射来破坏单例
public static void main(String[] args) {
Class<?> clzz = EunmSingleton.class;
try {
Constructor constructor = clzz.getDeclaredConstructor(null);//获取构造方法
constructor.setAccessible(true);//授权
Object o1 = constructor.newInstance(null);//通过反射得到的构造方法获取实例1
Object o2 = EunmSingleton.getInstance();//通过正常调用获取实例2
System.out.println(o1);
System.out.println(o2);
System.out.println(o1==o2);//直接比较地址是否相同
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("--------main线程结束---------");
}
运行结果:报错,找不到构造方法
原因
通过jad反编译EunmSingleton.class,得到EunmSingleton.jad
打开EunmSingleton.jad可以看到以下内容:
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: EunmSingleton.java
public final class EunmSingleton extends Enum
{
public static EunmSingleton[] values()
{
return (EunmSingleton[])$VALUES.clone();
}
public static EunmSingleton valueOf(String name)
{
return (EunmSingleton)Enum.valueOf(com/geekbang/gupao/vip/parton/singleton/regist/EunmSingleton, name);
}
private EunmSingleton(String s, int i)
{
super(s, i);
}
public static EunmSingleton getInstance()
{
return INSTANCE;
}
public Object getData()
{
return data;
}
public void setData(Object data)
{
this.data = data;
}
public static final EunmSingleton INSTANCE;
private Object data;
private static final EunmSingleton $VALUES[];
//饿汉式,线程安全
static
{
INSTANCE = new EunmSingleton("INSTANCE", 0);
$VALUES = (new EunmSingleton[] {
INSTANCE
});
}
}
为什么会找不到该方法:因为在反编译的代码中看到,EunmSingleton的无参构造方法不存在,只存在有参构造方法
private EunmSingleton(String s, int i)
{
super(s, i);
}
2.再次尝试通过有参构造方法反射破坏单例
public static void main(String[] args) {
Class<?> clzz = EunmSingleton.class;
try {//尝试调用有参构造方法
Constructor constructor = clzz.getDeclaredConstructor(String.class,int.class);//获取构造方法
constructor.setAccessible(true);//授权
EunmSingleton o1 = (EunmSingleton) constructor.newInstance("zhangsan",666);//通过反射得到的构造方法获取实例1
EunmSingleton o2 = EunmSingleton.getInstance();//通过正常调用获取实例2
System.out.println(o1);
System.out.println(o2);
System.out.println(o1==o2);//直接比较地址是否相同
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("--------main线程结束---------");
}
结果
报错参数异常,不能通过反射找到这个枚举对象
1.进入newInstance这个方法
可以看出,这一句判断这个对象是不是枚举类,如果是,则报错,如果不是,则继续执行。所以从JDK层面就在为枚举不被反射和序列化破坏而护航,所以推荐使用枚举实现单例模式!
3.尝试用序列化破坏单例
测试代码:
public static void main(String[] args) {
EunmSingleton lazy1 = null;
EunmSingleton lazy2 = EunmSingleton.getInstance();
lazy2.setData(new Object());
FileOutputStream fos = null;
try {
//----------------将lazy2通过序列化写入磁盘--------------------------
fos = new FileOutputStream("EunmSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(lazy2);
oos.flush();
oos.close();
//----------------将从磁盘反序列化读入一个实例lazy1---------------------
FileInputStream fis = new FileInputStream("EunmSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
lazy1 = (EunmSingleton) ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(lazy1.getData());
System.out.println(lazy2.getData());
System.out.println(lazy1.getData()==lazy2.getData());
}
测试结果:
lazy1与lazy2地址相同,故序列化的方式未能破坏单例
原因
还是通过源码查看
1.在readObject0这段代码中,枚举类型会走到
2.进入readEnum方法,发现是通过class对象加一个枚举类的名字,在内存中查找对应的枚举对象。由于枚举对象在JVM中只会被加载一次,所以查找到的这个枚举一定是同一个。最后将这个枚举类返给1中的checkResolve方法。
3.在checkResolve中,resolveObject的源码显示,直接将传进来的枚举返回给了rep,并最终返回给了测试代码中的lazy1,所以lazy1与lazy2是完全相同的,保证了单例不被破坏。