单例模式以及通过反射和序列化破解单例模式
对于Java来说单例模式可以有以下几种方式:
破解单例模式有两种方式:通过反射的方式和通过序列化的方式。下面将一一对此进行分析。
饿汉方式非常简单,即使用一个初始化的静态变量,代码如下:
1
public
?class
?EagerSingleton {
2
???
private
?static
?final
?EagerSingleton instance =?
new
?EagerSingleton();
3
?4
???
private
?EagerSingleton(){}
5
?6
???
public
?static
?EagerSingleton getInstance() {
7
??????
return
?instance;
8
???
}
9
}
懒汉模式和饿汉模式类似,只是静态变量定义时不进行初始化,调用getInstance()时才进行初始化,这就需要考虑多线程时问题,使用synchronized关键字修饰方法即可:
01
class
?LazySingleton {
02
????
private
?static
?LazySingleton instance =?
null
;
03
????
private
?LazySingleton() {}
04
????
public
?static
?synchronized
?LazySingleton getInstance() {
05
????????
if
?(instance ==?
null
) {
06
????????????
instance =?
new
?LazySingleton();
07
????????
}
08
????????
return
?instance;
09
????
}
10
}
懒汉模式实际是一种懒加载,但是为了避免多线程时单例失效,必须对getInstance()方法进行同步。可以使用双重检查方式来避免对方法全部进行加锁:
01
class
?DoubleCheckSingleton {
02
????
private
?static
?volatile
?DoubleCheckSingleton instance =?
null
;
03
????
private
?DoubleCheckSingleton() {}
04
????
public
?static
?DoubleCheckSingleton getInstance() {
05
????????
if
?(instance ==?
null
) {
06
????????????
synchronized
?(DoubleCheckSingleton.
class
) {
07
????????????????
if
?(instance ==?
null
) {
08
????????????????????
instance =?
new
?DoubleCheckSingleton();
09
????????????????
}
10
????????????
}
11
????????
}
12
????????
return
?instance;
13
????
}
14
}
这个需要几个注意点:静态类变量必须声明为private static?volatile,这样可以?写入操作?happens-before?于每一个后续的同一个字段的读操作。在getInstance()方法中,第一次判断null后,使用同步防止多线程时破坏单例。
?
第四种是使用内部类的方式,也是一种懒加载的方式实现:
?
1
class
?InnerClassLazySingleton {
2
????
private
?static
?class
?SingletonCreator {
3
????????
private
?static
?final
?InnerClassLazySingleton instance =?
new
?InnerClassLazySingleton();
4
????
}
5
????
public
?static
?InnerClassLazySingleton getInstance() {
6
????????
return
?SingletonCreator.instance;
7
????
}
8
}
内部类只在第一次调用的时候才会被类加载器加载,实现了懒加载
同时由于instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性
但是上面四种方式都可以通过反射的方式来破坏单例:
1
InnerClassLazySingleton eagerSingleton = InnerClassLazySingleton.getInstance();
2
Constructor<InnerClassLazySingleton> constructor = InnerClassLazySingleton.
class
.getDeclaredConstructor();
3
constructor.setAccessible(
true
);
4
?5
InnerClassLazySingleton eagerSingletonFromRef = constructor.newInstance();
6
System.out.println(
"使用反射破解饥渴单例模式:"
?+ (eagerSingleton == eagerSingletonFromRef ??
"否"
?:?
"是"
));
并且如果实现了Serializable接口的话,通过序列化的方式也可以破坏单例:
1
ByteArrayOutputStream baos? =?
new
?ByteArrayOutputStream();
2
ObjectOutputStream oos =?
new
?ObjectOutputStream(baos);
3
oos.writeObject(eagerSingleton);
4
?5
ObjectInputStream ois =?
new
?ObjectInputStream(
new
?ByteArrayInputStream(baos.toByteArray()));
6
InnerClassLazySingleton eagerSingletonFromSerial =? (InnerClassLazySingleton) ois.readObject();
7
System.out.println(
"使用序列化破解饥渴单例模式:"
?+ (eagerSingleton == eagerSingletonFromSerial ??
"否"
?:?
"是"
));
可以通过在单例类中添加readResolve()方法的方式来解决:
01
class
?EagerSingleton?
implements
?Serializable {
02
????
private
?static
?final
?EagerSingleton instance =?
new
?EagerSingleton();
03
????
private
?EagerSingleton() {}
04
????
public
?static
?EagerSingleton getInstance() {
05
????????
return
?instance;
06
????
}
07
?08
????
private
?Object readResolve() {
09
????????
System.out.println(
"print from readResolve() method"
);
10
????????
return
?getInstance();
11
????
}
12
?13
????
private
?void
?readObject(ObjectInputStream ois)?
throws
?IOException, ClassNotFoundException {
14
????????
System.out.println(
"print from readObject() method"
);
15
????????
ois.defaultReadObject();
16
????
}
17
}
readResolve()方法是用来替换从流中读取的对象的,它在readObject(ObjectInputStream)方法之后被调用,readObject()方法即从流中读取对象的方法。这样就可以避免使用序列化方式破坏单例。
?
但是上面的四种方式还是无法避免反射的方式来破坏单例的情况,可以使用枚举的方式实现单例:
1
enum
?EnumSingleton {
2
????
INSTANCE;
3
????
?4
????
public
?static
?void
?method() {
5
????????
?6
????
}
7
}
通过反射方式创建实例时会抛出?Exception in thread "main" java.lang.NoSuchMethodException: net.local.singleton.EnumSingleton.<init>()?异常。并且使用枚举类Enum实现了序列化接口,并且也实现了readResolve()方法,因为使用序列化方式也没法破坏,是最理想的单例模式实现。?