单例模式就是在整个全局中(无论是单线程还是多线程),该对象只存在一个实例,而且只应该存在一个实例,没有副本(副本的制作需要花时间和空间资源)。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,同时该对象需要协调系统整体的行为,单例模式是最好的解决方案。单例模式相当于只有一个入口的系统,使得所有想要获取该系统资源的对象都要经过该入口。
单例实现的方式及优缺点
/** * Created by Chenay on 2016/4/5. */ public class Singleton implements Serializable{ private static Singleton singleton; transient int i; //短暂的 /** * 单例模式1 * 优点:最为简单;之所以称为懒汉,是因为它把单例初始化延迟到第一次调用 * 缺点:线程不安全 * * @return */ public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } /** * 优点:线程安全 * 缺点:99%的同步是多与的 * * @param s * @return */ public static synchronized Singleton getInstance(String s) { if (singleton == null) { singleton = new Singleton(); } return singleton; } /** * 优点:单例唯一,线程安全 * 缺点:JDK1.6以上;第一次加载比较慢;偶尔会失败(双重检查锁定失效) * * @param i * @return */ private static Object synObj = new Object(); public static Singleton getInstance(int i) { if (instance == null) {//先判断有没有实例化 synchronized (synObj) {//如果没有被实例化就请求锁 if (instance == null) {//得到锁之后,再次判断是否已经被先前获得锁的对象实例化 return instance = new Singleton(); } } } return instance; } /** * 优点:代码简洁,不需要同步锁 * 缺点:之所以称为饿汉,是因为只要编译器一看到该类就会初始化单例,无法达到 * 延迟加载的目的。 */ private static Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance(int i, int synObj) { return instance; } /** * 优点:线程安全,对象唯一性,延迟实例化 * 缺点:暂无 */ private static class SingletonHolder { private static final Singleton SINGLETON = new Singleton(); } public static Singleton getInstence() { return SingletonHolder.SINGLETON; } //添加这个hook函数,那么系统在反序列化的过程中就会通过该Hook方法得到原有的单例 //而不是重新创建一个单例。 private Object readResolve() throws ObjectStreamException { return SingletonHolder.SINGLETON; }
序列化对单例的破坏
首先来写一个单例的类:
code 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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
|
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方法,并在该方法中指定要返回的对象的生成策略,就可以方式单例被破坏。