小小的單例模式看著簡單听哭,其實(shí)里面道道著實(shí)不少。不僅要在多線程下保證實(shí)例唯一塘雳,也要能抵御序列化以及反射對單例的破壞陆盘。
要同時解決這么多問題,說難也難败明,說簡單也簡單隘马。在《Effective Java》這本書中最推薦的單例寫法就是使用枚舉來實(shí)現(xiàn)單例。枚舉類天然的能同時滿足多線程安全問題以及抵御序列化和反射對單例的破壞的問題妻顶。
枚舉單例實(shí)現(xiàn)
/**
* @Author: ming.wang
* @Date: 2019/2/22 14:05
* @Description: 枚舉實(shí)現(xiàn)單例
*/
public enum EnumInstance {
INSTANCE,;
private Date birthDay;
public Date getBirthDay() {
return birthDay;
}
public void setBirthDay(Date birthDay) {
this.birthDay = birthDay;
}
public static EnumInstance getInstance()
{
return INSTANCE;
}
}
分析
下面我們分析一下酸员,為什么枚舉類能起到如此“逆天”的功能蜒车。
- 枚舉vs多線程安全
首先我們分析一下,為什么枚舉類可以保證線程安全幔嗦。此處我們需要用到一個很牛叉的反編譯工具jad,可支持linux酿愧、windows和蘋果系統(tǒng)。下載完之后邀泉,我們使用jad來反編譯一下EnumInstance.class(命令為jad \..\EnumInstance.class
)嬉挡,然后生成了EnumInstance.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: EnumInstance.java
package com.wangming.pattern.creational.singleton;
import java.util.Date;
public final class EnumInstance extends Enum
{
public static EnumInstance[] values()
{
return (EnumInstance[])$VALUES.clone();
}
public static EnumInstance valueOf(String name)
{
return (EnumInstance)Enum.valueOf(com/wangming/pattern/creational/singleton/EnumInstance, name);
}
private EnumInstance(String s, int i)
{
super(s, i);
}
public Date getBirthDay()
{
return birthDay;
}
public void setBirthDay(Date birthDay)
{
this.birthDay = birthDay;
}
public static EnumInstance getInstance()
{
return INSTANCE;
}
public static final EnumInstance INSTANCE;
private Date birthDay;
private static final EnumInstance $VALUES[];
static
{
INSTANCE = new EnumInstance("INSTANCE", 0);
$VALUES = (new EnumInstance[] {
INSTANCE
});
}
}
通過反編譯之后汇恤,一切秘密盡在眼前庞钢,原來它內(nèi)部是執(zhí)行了靜態(tài)代碼塊,和餓漢式代碼有異曲同工之妙因谎,前面我們分析了當(dāng)一個Java類第一次被真正使用到的時候靜態(tài)資源被初始化基括、Java類的加載和初始化過程都是線程安全的。所以财岔,創(chuàng)建一個enum類型是線程安全的风皿。
- 枚舉vs序列化和反序列化
我們貼上完整的代碼測試
package com.wangming.pattern.creational.singleton;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Date;
/**
* @Author: ming.wang
* @Date: 2019/2/21 16:05
* @Description: 使用反射或反序列化來破壞單例
*/
public class DestroySingletonTest {
public static void main(String[] args) throws Exception {
//序列化方式破壞單例 測試
serializeDestroyMethod();
//反射方式破壞單例模式 測試
// reflectMethod();
}
private static void reflectMethod() throws Exception {
// reflectHungryMethod();
// reflectLazyMethod();
reflectLazyMethod2();
}
private static void reflectHungryMethod() throws Exception {
//同理StaticInnerClassSingleton
HungrySingleton hungrySingleton = null;
HungrySingleton hungrySingleton_new = null;
Class singletonClass = HungrySingleton.class;
Constructor declaredConstructor = singletonClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
hungrySingleton = HungrySingleton.getInstance();
hungrySingleton_new = (HungrySingleton) declaredConstructor.newInstance();
System.out.println(hungrySingleton == hungrySingleton_new);
}
/**
* 驗(yàn)證使用對象空判斷是否可抵御反射攻擊
* @throws Exception
*/
private static void reflectLazyMethod() throws Exception {
LazySingleton lazySingleton = null;
LazySingleton lazySingleton_new = null;
Class singletonClass = LazySingleton.class;
Constructor declaredConstructor = singletonClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
lazySingleton = LazySingleton.getInstance();
lazySingleton_new = (LazySingleton) declaredConstructor.newInstance();
System.out.println(lazySingleton == lazySingleton_new);
}
/**
* 驗(yàn)證使用標(biāo)志位是否可抵御反射攻擊
* @throws Exception
*/
private static void reflectLazyMethod2() throws Exception {
LazySingleton lazySingleton = null;
LazySingleton lazySingleton_new = null;
Class singletonClass = LazySingleton.class;
Constructor declaredConstructor = singletonClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
lazySingleton_new = (LazySingleton) declaredConstructor.newInstance();
Field flag = singletonClass.getDeclaredField("flag");
flag.setAccessible(true);
flag.set(lazySingleton_new,true);
lazySingleton = LazySingleton.getInstance();
System.out.println(lazySingleton == lazySingleton_new);
}
private static void serializeDestroyMethod() throws IOException, ClassNotFoundException {
// HungrySingleton intance=null;
// HungrySingleton intance_new=null;
// StaticInnerClassSingleton intance = null;
// StaticInnerClassSingleton intance_new = null;
EnumInstance intance = null;
EnumInstance intance_new = null;
// hungrySingleton=HungrySingleton.getInstance();
// intance = StaticInnerClassSingleton.getInstance();
intance=EnumInstance.getInstance();
intance.setBirthDay(new Date());
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(intance);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
// hungrySingleton_new= (HungrySingleton) ois.readObject();
// intance_new = (StaticInnerClassSingleton) ois.readObject();
intance_new = (EnumInstance) ois.readObject();
System.out.println(intance == intance_new);
System.out.println(intance.getBirthDay() == intance_new.getBirthDay());
}
}
運(yùn)行結(jié)果是兩個true昌抠。原因我們也簡單提示一下,在討論單例模式的攻擊之序列化與反序列化這篇文章中炊苫,我們分析了ObjectInputStream.readObject()方法裁厅,其中一處代碼
....
case TC_ENUM:
return checkResolve(readEnum(unshared));
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
....
很顯然我們此時是要走case TC_ENUM: return checkResolve(readEnum(unshared));
這個分支。然后看readEnum(unshared)
方法侨艾,你就知道為啥了执虹。
- 枚舉vs反射
為啥枚舉能抵御幾乎萬能的反射呢?原因就在Constructor.newInstance()方法(Class.newInstance()最終調(diào)用的也是這個方法)唠梨。我們跟進(jìn)去看下源碼
....
@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;
}
.....
看到了嗎if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects");
當(dāng)判斷是枚舉類的時候袋励,就直接拋出異常了。
結(jié)論
上面的疑惑基本解開当叭,我們在運(yùn)用單例模式的時候茬故,最推薦的做法就是使用枚舉類來實(shí)現(xiàn)單例!