单例模式的破坏

反射破坏单例模式

破坏方法

在Java中,反射技术可以获取类的所有方法、成员变量,还能访问私有的变量和方法。所以private的构造器也是可以被获取到并执行的。这样一来,单例模式中的的私有构造器被反射多次调用时,就会创建多个实例,进而破坏单例模式。下面是sample:

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.github.archerda.designpattern.singleton.sync;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* 单例:利用同步实现
*
* Create by archerda on 2017/11/12.
*/
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
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
package com.github.archerda.designpattern.singleton.destory.reflection;
import com.github.archerda.designpattern.singleton.sync.Singleton;
import java.lang.reflect.Constructor;
/**
* 反射破坏单例模式
*
* Create by archerda on 2017/11/12.
*/
public class BreakingSingleton {
public static void main(String[] args) throws Exception {
// 常规方法获取实例
Singleton commonSingleton = Singleton.getInstance();
// 通过反射创建实例
Constructor constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton reflectionSingleton = (Singleton) constructor.newInstance();
// 这里会输出: false,证明两个实例不一样,单例模式被破坏了
System.out.println(reflectionSingleton == commonSingleton);
}
}

伪解决办法

反射也是要通过私有构造器来实例化,那可以在构造器里面加一个判断,如果已经实例化了,就抛出一个异常。看代码:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.github.archerda.designpattern.singleton.destory.reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* 利用实例化标志位解决反射破坏单例的问题
*
* Create by archerda on 2017/11/12.
*/
public class SolutionSingleton {
private static SolutionSingleton instance;
private static boolean isInit = false;
private SolutionSingleton() {
synchronized (SolutionSingleton.class) {
if (isInit) {
throw new RuntimeException("Singleton has been initialized. Can't initialize more.");
} else {
isInit = true;
}
}
}
public static synchronized SolutionSingleton getInstance() {
if (instance == null) {
instance = new SolutionSingleton();
}
return instance;
}
public static void main(String[] args) throws Exception {
// 常规方法获取实例
SolutionSingleton commonSolutionSingleton = SolutionSingleton.getInstance();
// 通过反射获取实例:这里会抛异常,因为只允许实例化一次;
Constructor constructor = SolutionSingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
SolutionSingleton reflectionSolutionSingleton = (SolutionSingleton) constructor.newInstance();
// 这里不会执行到
System.out.println(reflectionSolutionSingleton == commonSolutionSingleton);
}
}

main的执行结果:

1
2
3
4
5
6
7
8
9
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.github.archerda.designpattern.singleton.destory.reflection.SolutionSingleton.main(SolutionSingleton.java:39)
Caused by: java.lang.RuntimeException: Singleton has been initialized.
at com.github.archerda.designpattern.singleton.destory.reflection.SolutionSingleton.<init>(SolutionSingleton.java:18)
... 5 more

看起来效果达到了,常规方法实例化单例后,再用反射技术实例化,JVM抛出异常了,单例模式得到了保证。

但是,这样就真的万无一失了吗?
不!因为我们仍然可以用反射技术去修改静态变量isInit的值。来看客户端的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) throws Exception {
// 常规方法获取实例
SolutionSingleton commonSolutionSingleton = SolutionSingleton.getInstance();
// 通过反射修改静态变量isInit的值,然后用反射创建实例
Field isInitField = SolutionSingleton.class.getDeclaredField("isInit");
isInitField.setAccessible(true);
isInitField.set(null, false); // 修改isInit的值
Constructor constructor = SolutionSingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
SolutionSingleton reflectionSolutionSingleton = (SolutionSingleton) constructor.newInstance();
// 这里会输出: false,两个单例不一样,单例模式被破坏了
System.out.println(reflectionSolutionSingleton == commonSolutionSingleton);
}

序列化

破坏方法

除了反射外,序列化和反序列化也会破坏单例模式。我们直接看代码:

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
package com.github.archerda.designpattern.singleton.destory.serialization;
import java.io.Serializable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* 使用同步方式实现单例,并且实现序列化接口
*
* Create by archerda on 2017/11/12.
*/
public class Singleton implements Serializable {
private static final long serialVersionUID = -8646622406647124406L;
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

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
32
33
34
35
36
37
38
package com.github.archerda.designpattern.singleton.destory.serialization;
import java.io.*;
/**
* 序列化破坏单例模式;
*
* Create by archerda on 2017/11/12.
*/
public class BreakingSingleton {
public static void main(String[] args) throws Exception {
// 常规方法获取单例对象
Singleton commonSingleton = Singleton.getInstance();
// 序列化和反序列化获取单例对象
File file = File.createTempFile("SerializationBreakingSingleton", ".tmp");
System.out.println(file.getAbsolutePath());
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
// 把常规方法得到的实例序列化到文件中
oos.writeObject(commonSingleton);
oos.close();
fos.close();
FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);
// 从文件中反序列化得到实例
Singleton serializationSingleton = (Singleton) ois.readObject();
ois.close();
fis.close();
file.deleteOnExit();
System.out.println(commonSingleton);
System.out.println(serializationSingleton);
System.out.println(commonSingleton == serializationSingleton);
}
}

测试代码输出结果:

1
2
3
com.github.archerda.designpattern.singleton.destory.serialization.Singleton@f5f2bb7
com.github.archerda.designpattern.singleton.destory.serialization.Singleton@246ae04d
false

序列化的实例和反序列化出来的实例不是同一个,说明单例模式被破坏了。

解决办法

为了引出序列化破坏单例的解决方法,我们得先来看下实例是怎么被序列化和反序列化的。

对象的序列化过程是通过ObjectOutputStream和ObjectInputStream来实现的,那么我们来分析一下ObjectInputStream的readObject方法到底是怎么执行的。

通过跟踪堆栈,我们可以获取到反序列化时ObjectInputStream的readObject的调用栈:

readObject -> readObject0 -> readOrdinaryObject -> checkResolve

我们来重点看一下readOrdinaryObject方法的部分代码:

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
32
33
34
35
36
37
38
39
private Object readOrdinaryObject(boolean unshared) throws IOException {
...
// CodeBlock1
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);
}
...
// CodeBlock2
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) {
// Filter the replacement object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}

这里又分两个代码片段,我们先看CodeBlock1

1
2
3
4
5
6
7
8
9
// CodeBlock1
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方法返回的对象。

desc.isInstantiable():如果一个类实现了Serializable/Externalizable并且可以在运行时被序列化,那么该方法就返回true;
desc.newInstance():该方法通过反射技术调用无参构造器实例化一个对象;

所以,到这里,也就能明白为什么序列化可以破坏单例,对根到底,还是使用反射来实现的。

为了解决反序列化破坏单例,我们需要继续看CodeBlock2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// CodeBlock2
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) {
// Filter the replacement object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
return obj;

desc.hasReadResolveMethod():如果实现了Serializable或Externalizable的类中包含readResolve方法,则返回true;
desc.invokeReadResolve(obj):通过反射的方式调用被反序列化的类中的readResolve方法。

所以,为了解决序列化破坏的单例,我们只要在单例类中定义一个readResolve方法,并在该方法中指定要返回对象的生成策略,就可以防止单例被破坏了。下面是代码:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.github.archerda.designpattern.singleton.destory.serialization;
import java.io.*;
/**
* 通过定义 readResolve方法,防止反序列化破坏单例
*
* Create by archerda on 2017/11/12.
*/
public class SolutionSingleton implements Serializable {
private static final long serialVersionUID = -4799829623335297540L;
private static SolutionSingleton instance;
private SolutionSingleton() {}
public static synchronized SolutionSingleton getInstance() {
if (instance == null) {
instance = new SolutionSingleton();
}
return instance;
}
// 反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要单独再创建新对象
private Object readResolve() {
return instance;
}
public static void main(String[] args) throws Exception {
// 常规方法获取单例对象
SolutionSingleton commonSingleton = SolutionSingleton.getInstance();
// 序列化获取单例对象
File file = File.createTempFile("SerializationBreakingSingleton", "txt");
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(commonSingleton);
oos.close();
fos.close();
FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);
SolutionSingleton serializationSingleton = (SolutionSingleton) ois.readObject();
ois.close();
fis.close();
System.out.println(commonSingleton);
System.out.println(serializationSingleton);
// 这里会输出:true,单例模式没有被破坏
System.out.println(commonSingleton == serializationSingleton);
}
}

多个类加载器

类加载器,是用来把类(Class)装载进JVM的,JVM定义了两种类型的类加载器,一种是JVM缺省的加载器,包括Bootstrap加载器、Extension加载器和System加载器,另外一种是用户自定义的加载器,只要继承自java.lang.ClassLoader类就好了。所以,在一个JVM实例中,可能存在多个ClassLoader,每个ClassLoader拥有自己的命名空间namespace。一个ClassLoader只能拥有一个class对象类型的实例,但是不同的ClassLoader可能拥有多个class对象的实例,这会对单例模式产生致命的问题。比如ClassLoaderA装载类A的实例A1,而ClassLoaderB也装载了类A的实例A2,本来A1==A2应该是true的,但是由于A1和A2来自不同的ClassLoader,它们实际上是完全不同的,如果A中定义了一个静态变量c,那么c在不同的ClassLoader的值是不同的。

为了解决这个问题,我们要做的是:

  • 要么保证“多个ClassLoader不会装载同一个单例类”;
  • 要么让多个ClassLoader继承自同一个父ClassLoader,这样多个ClassLoader都会通过同一个父ClassLoader去装载单例类,进而避免装载多个单例类;

参考文档