所谓单例模式就是,某一个类只能有一个实例,实现的核心就是将类的构造函数私有化,只能由该类创建对象,其他对象就不能调用该类的构造函数,即不能创建对象了。
现在看一个问题:对象的创建方式有哪几种?
四种:new 、克隆、序列化、反射。
其实上面的说法有点问题,改为:……其他对象就不能调用该类的构造函数,即不能通过new 来创建对象了。那么是否还有可以通过其他的三种方式创建对象呢,即其他三种方式会不会破坏单例模式呢?
克隆可能对单例模式的破坏
由克隆我们可以想到原型模式,原型模式就是通过clone方法实现对象的创建的,clone方式是Object方法,每个对象都有,那我使用一个单例模式类的对象,调用clone方法,再创建一个新的对象了,那岂不是上面说的单例模式失效了。当然答案是否定,某一个对象直接调用clone方法,会抛出异常,即并不能成功克隆一个对象。调用该方法时,必须实现一个Cloneable 接口。这也就是原型模式的实现方式。还有即如果该类实现了cloneable接口,尽管构造函数是私有的,他也可以创建一个对象。即clone方法是不会调用构造函数的,他是直接从内存中copy内存区域的。所以单例模式的类是不可以实现cloneable接口的。
序列化可能对单例模式的破坏
一是可以实现数据的持久化;二是可以对象数据的远程传输。
如果过该类implements Serializable,那么就会在反序列化的过程中再创一个对象。这个问题的解决办法就是在反序列化时,指定反序化的对象实例。添加如下方法:
private static final long serialVersionUID = -3979059770681747301L;
private volatile static Singleton singleton;
private Object readResolve() {
return singleton;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 反射可能对单例模式的破坏
反射是可以获取类的构造函数,再加一行 setAccessible(true);就可以调用私有的构造函数,创建对象了。那么防止反射破坏Java单例模式的方法就是:当第二次调用构造函数时抛出异常。代码如下:
private volatile static Singleton1 singleton;
private static boolean flag = true;
private Singleton1 (){
if(flag){
flag = false;
}else{
throw new RuntimeException("单例模式险些被破坏,第二个对象未创建成功");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
还有一种方式是通过枚举实现。
本文将通过实例+阅读Java源码的方式介绍序列化是如何破坏单例模式的,以及如何避免序列化对单例的破坏。
单例模式,是设计模式中最简单的一种。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。关于单例模式的使用方式,可以阅读单例模式的七种写法
但是,单例模式真的能够实现实例的唯一性吗?
答案是否定的,很多人都知道使用反射可以破坏单例模式,除了反射以外,使用序列化与反序列化也同样会破坏单例。
序列化对单例的破坏
首先来写一个单例的类:
code 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package
com.hollis;
import
java.io.Serializable;
/**
* Created by hollis on 16/2/5.
* 使用双重校验锁方式实现单例
*/
public
class
Singleton
implements
Serializable{
private
volatile
static
Singleton singleton;
private
Singleton (){}
public
static
Singleton getSingleton() {
if
(singleton ==
null
) {
synchronized
(Singleton.
class
) {
if
(singleton ==
null
) {
singleton =
new
Singleton();
}
}
}
return
singleton;
}
}
|
接下来是一个测试类:
code 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
package
com.hollis;
import
java.io.*;
/**
* Created by hollis on 16/2/5.
*/
public
class
SerializableDemo1 {
//为了便于理解,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记
//Exception直接抛出
public
static
void
main(String[] args)
throws
IOException, ClassNotFoundException {
//Write Obj to file
ObjectOutputStream oos =
new
ObjectOutputStream(
new
FileOutputStream(
"tempFile"
));
oos.writeObject(Singleton.getSingleton());
//Read Obj from file
File file =
new
File(
"tempFile"
);
ObjectInputStream ois =
new
ObjectInputStream(
new
FileInputStream(file));
Singleton newInstance = (Singleton) ois.readObject();
//判断是否是同一个对象
System.out.println(newInstance == Singleton.getSingleton());
}
}
//false
|
输出结构为false,说明:
通过对Singleton的序列化与反序列化得到的对象是一个新的对象,这就破坏了Singleton的单例性。
这里,在介绍如何解决这个问题之前,我们先来深入分析一下,为什么会这样?在反序列化的过程中到底发生了什么。
ObjectInputStream
对象的序列化过程通过ObjectOutputStream和ObjectInputputStream来实现的,那么带着刚刚的问题,分析一下ObjectInputputStream 的readObject
方法执行情况到底是怎样的。
为了节省篇幅,这里给出ObjectInputStream的readObject
的调用栈:
readObject--->readObject0--->readOrdinaryObject--->checkResolve
这里看一下重点代码,readOrdinaryObject
方法的代码片段:
code 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
private
Object readOrdinaryObject(
boolean
unshared)
throws
IOException
{
//此处省略部分代码
Object obj;
try
{
obj = desc.isInstantiable() ? desc.newInstance() :
null
;
}
catch
(Exception ex) {
throw
(IOException)
new
InvalidClassException(
desc.forClass().getName(),
"unable to create instance"
).initCause(ex);
}
//此处省略部分代码
if
(obj !=
null
&&
handles.lookupException(passHandle) ==
null
&&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
if
(unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if
(rep != obj) {
handles.setObject(passHandle, obj = rep);
}
}
return
obj;
}
|
code 3 中主要贴出两部分代码。先分析第一部分:
code 3.1
1
2
3
4
5
6
|
Object obj;
try
{
obj = desc.isInstantiable() ? desc.newInstance() :
null
;
}
catch
(Exception ex) {
throw
(IOException)
new
InvalidClassException(desc.forClass().getName(),
"unable to create instance"
).initCause(ex);
}
|
这里创建的这个obj对象,就是本方法要返回的对象,也可以暂时理解为是ObjectInputStream的readObject
返回的对象。
isInstantiable
:如果一个serializable/externalizable的类可以在运行时被实例化,那么该方法就返回true。针对serializable和externalizable我会在其他文章中介绍。
desc.newInstance
:该方法通过反射的方式调用无参构造方法新建一个对象。
所以。到目前为止,也就可以解释,为什么序列化可以破坏单例了?
答:序列化会通过反射调用无参数的构造方法创建一个新的对象。
那么,接下来我们再看刚开始留下的问题,如何防止序列化/反序列化破坏单例模式。
防止序列化破坏单例模式
先给出解决方案,然后再具体分析原理:
只要在Singleton类中定义readResolve
就可以解决该问题:
code 4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package
com.hollis;
import
java.io.Serializable;
/**
* Created by hollis on 16/2/5.
* 使用双重校验锁方式实现单例
*/
public
class
Singleton
implements
Serializable{
private
volatile
static
Singleton singleton;
private
Singleton (){}
public
static
Singleton getSingleton() {
if
(singleton ==
null
) {
synchronized
(Singleton.
class
) {
if
(singleton ==
null
) {
singleton =
new
Singleton();
}
}
}
return
singleton;
}
private
Object readResolve() {
return
singleton;
}
}
|
具体原理,我们回过头继续分析code 3中的第二段代码:
code 3.2
1
2
3
4
5
6
7
8
9
10
11
12
|
if
(obj !=
null
&&
handles.lookupException(passHandle) ==
null
&&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
if
(unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if
(rep != obj) {
handles.setObject(passHandle, obj = rep);
}
}
|
hasReadResolveMethod
:如果实现了serializable 或者 externalizable接口的类中包含readResolve
则返回true
invokeReadResolve
:通过反射的方式调用要被反序列化的类的readResolve方法。
所以,原理也就清楚了,主要在Singleton中定义readResolve方法,并在该方法中指定要返回的对象的生成策略,就可以方式单例被破坏。
总结
在涉及到序列化的场景时,要格外注意他对单例的破坏。