单例模式有好几种实现方式:饿汉式、懒汉式、DCL、静态内部类、枚举等。
本文主要探讨几种实现方式是否实现了真正意义上的单例——只创建一个对象。
创建对象有这么几种方式:new、克隆、序列化、反射。
几种单例模式实现时都会将构造函数声明为private,所以new这种方式此时认为不可用,那么着重看其他几种是否能保证真正的单例。
序列化
@Slf4j
public class Singleton implements Serializable{
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
public static void main(String[] args) throws Exception {
Singleton singleton = Singleton.getInstance();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("SingletonTest"));
objectOutputStream.writeObject(singleton);
objectOutputStream.close();
File file = new File("SingletonTest");
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
Singleton newSingleton = (Singleton)objectInputStream.readObject();
file.delete();
log.info("反序列化后是否和原对象相同:{}", singleton == newSingleton);//false
}
}
可以看到反序列化会生成一个新的对象。
原因就在反序列化时调用的readObject方法。debug进去看,调用链为readObject->readObject0->readOrdinaryObject
obj = desc.isInstantiable() ? desc.newInstance() : null;
boolean isInstantiable() {
requireInitialized();
return (cons != null);//当类实现了Serializable接口时,cons会被初始化为一个无参构造函数
}
Object newInstance()
throws InstantiationException, InvocationTargetException,
UnsupportedOperationException
{
requireInitialized();
if (cons != null) {
try {
//构造函数调用newInstance()生成新对象
return cons.newInstance();
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
throw new InternalError(ex);
}
} else {
throw new UnsupportedOperationException();
}
}
最终还是通过无参构造函数调用newInstance()生成新对象,所以反序列化会生成一个新的对象。序列化破坏了单例。
生成对象的下面还有一段代码
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())//是否覆写了readResolve方法
{
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
handles.setObject(passHandle, obj = rep);
}
}
判断是否实现了readResolve方法,如果实现了,则调用readResolve方法,生成rep对象,将刚才通过反射得到的对象替换掉。所以我们可以这么写
@Slf4j
public class Singleton implements Cloneable, Serializable{
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
//覆写readResolve方法
public Object readResolve() {
return INSTANCE;
}
public static void main(String[] args) throws Exception {
Singleton singleton = Singleton.getInstance();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("SingletonTest"));
objectOutputStream.writeObject(singleton);
objectOutputStream.close();
File file = new File("SingletonTest");
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
Singleton newSingleton = (Singleton)objectInputStream.readObject();
file.delete();
log.info("反序列化后是否和原对象相同:{}", singleton == newSingleton);//true
}
}
readResolve返回单例,所以最终输出结果为true。
但是通过readResolve来解决序列化的问题就安全了吗,没有,参见《Effective Java》,具体原因后面再说。
克隆
@Slf4j
public class Singleton implements Cloneable {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static Singleton getInstance() {
return INSTANCE;
}
public static void main(String[] args) throws Exception {
Singleton singleton = Singleton.getInstance();
log.info("克隆后是否和原对象相同:{}", singleton == singleton.clone());//false
}
}
克隆也会生成一个新的对象,克隆破坏了单例。但是我们可以让覆写的clone方法返回INSTANCE。
@Slf4j
public class Singleton implements Cloneable {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
@Override
protected Object clone() throws CloneNotSupportedException {
return INSTANCE;
}
public static Singleton getInstance() {
return INSTANCE;
}
public static void main(String[] args) throws Exception {
Singleton singleton = Singleton.getInstance();
log.info("克隆后是否和原对象相同:{}", singleton == singleton.clone());//true
}
}
此时,克隆出的对象和之前的一样。所以要么不实现Cloneable接口,要么覆写clone方法,返回已生成的单例。
反射
前面说这么多,在反射面前都跟纸一样,构造函数不是声明为private的了吗,简单,反射下,就拿到了
@Slf4j
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
public static void main(String[] args) throws Exception {
Singleton singleton = Singleton.getInstance();
Class clazz = Singleton.class;
Constructor declaredConstructor = clazz.getDeclaredConstructor();//获得无参构造函数
declaredConstructor.setAccessible(true);//破除private限制
Singleton singleton1 = (Singleton) declaredConstructor.newInstance();
log.info("反射后是否和原对象相同:{}", singleton == singleton1);//false
}
}
就算在构造函数加标记位防止二次调用,依然可以用反射破解。反射破坏了单例。
前面都是以饿汉式举例,懒汉式、静态内部类、DCL都一样。唯独枚举特殊,下面单独列出。
枚举序列化
public enum EnumSingleton {
INSTANCE
}
@Slf4j
public class Singleton {
public static void main(String[] args) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("SingletonTest"));
objectOutputStream.writeObject(EnumSingleton.INSTANCE);
objectOutputStream.close();
File file = new File("SingletonTest");
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
EnumSingleton newSingleton = (EnumSingleton)objectInputStream.readObject();
file.delete();
log.info("枚举反序列化后是否和原对象相同:{}", newSingleton == EnumSingleton.INSTANCE);//true
}
}
枚举反序列化后没有生成新的对象。看下为什么?关键代码如下
//readObject0方法中的关键代码
return checkResolve(readEnum(unshared));
//readEnum方法中的关键代码
Enum<?> en = Enum.valueOf((Class)cl, name);
result = en;
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
Map<String, T> enumConstantDirectory() {
if (enumConstantDirectory == null) {
T[] universe = getEnumConstantsShared();
if (universe == null)
throw new IllegalArgumentException(
getName() + " is not an enum type");
Map<String, T> m = new HashMap<>(2 * universe.length);
for (T constant : universe)
m.put(((Enum<?>)constant).name(), constant);
enumConstantDirectory = m;
}
return enumConstantDirectory;
}
T[] getEnumConstantsShared() {
if (enumConstants == null) {
if (!isEnum()) return null;
try {
final Method values = getMethod("values");
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
values.setAccessible(true);
return null;
}
});
@SuppressWarnings("unchecked")
T[] temporaryConstants = (T[])values.invoke(null);
enumConstants = temporaryConstants;
}
// These can happen when users concoct enum-like classes
// that don't comply with the enum spec.
catch (InvocationTargetException | NoSuchMethodException |
IllegalAccessException ex) { return null; }
}
return enumConstants;
}
最终调用的Enum的valueOf方法,枚举的相关特性参见(枚举),name传入的就是单例的名称(这里是INSTANCE)。getEnumConstantsShared方法通过反射拿到values方法,调用values方法得到存放枚举的数组。enumConstantDirectory拿到数据后,将其中的name做为key,实例作为value存入map中,valueOf通过name从map中取到对应的实例。在这个过程中,并没有生成新的实例,反序列化没有破坏枚举的单例。
枚举克隆
public enum EnumSingleton implements Cloneable {
INSTANCE;
@Override//会报错,无法覆写clone方法
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}
//Enum.java
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
当你尝试覆写clone方法的时候,会报错,因为enum其实是继承了Enum类的,而Enum中将clone方法声明为final方法了,无法覆写。枚举天然不支持clone,更不会被克隆破坏单例。
枚举反射
@Slf4j
public class Singleton {
public static void main(String[] args) throws Exception {
Class clazz = EnumSingleton.class;
Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
EnumSingleton newSingleton = (EnumSingleton) declaredConstructor.newInstance("INSTANCE", 0);
log.info("枚举反射后是否和原对象相同:{}", newSingleton == EnumSingleton.INSTANCE);
}
}
//报错
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
尝试对枚举进行反射的时候会报错,看下为什么。
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;
}
newInstance方法中会判断是否为枚举类型,如果是枚举,直接抛出异常,也就是上面打出的异常信息。反射无法破坏枚举的单例。
总结
- 饿汉式、懒汉式、静态内部类、DCL等实现单例的方式会被序列化/反序列化破坏,一个解决方法是重写readReslove方法返回已生成的单例。克隆也会破坏单例,所以要么不实现Cloneable,要么重写clone方法返回已生成的单例。反射可以破坏单例,生成新实例。
- 枚举天然不支持克隆,反射。序列化/反序列化时利用Enum的valueOf保证单例不被破坏。《Efficient Java》也建议枚举是实现单例的最佳方式。