设计模式-如何防止单例模式被破坏

单例模式是否安全

  在设计模式-单例模式这篇博客介绍了单例模式的几种实现方式,但是按照上述方式是否能够保证一定是单例呢?答案是否定的,如果不加限制,通过反射和序列化可以破坏单例模式。

破坏单例模式

反射

  单例模式把类的构造方法设置为private访问级别,导致外部类无法新建对象,通过反射我们可以修改访问级别,从外部新建单例类对象。以饿汉模式为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class StarveSingleton {

private static StarveSingleton instance = new StarveSingleton();

private StarveSingleton(){}

public static StarveSingleton getInstance() {
return instance;
}


public static void main(String[] args) {
//输出true
System.out.println(StarveSingleton.getInstance() == StarveSingleton.getInstance());
Class clazz = StarveSingleton.class;
Constructor constructor= clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
StarveSingleton instance = (StarveSingleton) constructor.newInstance(null);
//输出false
System.out.println(instance == StarveSingleton.getInstance());
}
}

  从结果来看,通过反射的方式,单例模式被破坏,我们可以随意产生新的单例类实例。

序列化

  如果单例类继承了serializable接口,那么序列化也可以破坏单例模式。依然以饿汉模式为例

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

public class StarveSingleton implements Serializable{

private static StarveSingleton instance = new StarveSingleton();

private StarveSingleton(){}

public static StarveSingleton getInstance() {
return instance;
}

public static void main(String[] args) throws IOException, ClassNotFoundException {

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
StarveSingleton starveSingleton = StarveSingleton.getInstance();
objectOutputStream.writeObject(starveSingleton);

byte[] bytes = outputStream.toByteArray();

ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(bytes));
StarveSingleton instance = (StarveSingleton) inputStream.readObject();
//输出false
System.out.println(starveSingleton == 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

public class StarveSingleton implements Serializable {
//设置flag
private static boolean flag = false;

private static StarveSingleton instance = new StarveSingleton();

private StarveSingleton() {
synchronized (StarveSingleton.class) {
//第一次调用的时候更改该变量的值,第二次再调用会报错
if (!flag) {
flag = !flag;
} else {
throw new RuntimeException("单例模式被破坏");
}
}
}

public static StarveSingleton getInstance() {
return instance;
}


public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

System.out.println(StarveSingleton.getInstance() == StarveSingleton.getInstance());
Class<StarveSingleton> clazz = StarveSingleton.class;
Constructor<StarveSingleton> constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
StarveSingleton instance = constructor.newInstance();
System.out.println(instance == StarveSingleton.getInstance());

}
}

  这样就解决了反射破坏单例的问题。

序列化

  对于序列化来说,需要实现readSolve()方法来保证每次返回的都是一个实例

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

public class StarveSingleton implements Serializable {

private static StarveSingleton instance = new StarveSingleton();

private StarveSingleton() {

}

public static StarveSingleton getInstance() {
return instance;
}


public Object readResolve() {
return instance;
}

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
StarveSingleton starveSingleton = StarveSingleton.getInstance();
objectOutputStream.writeObject(starveSingleton);

byte[] bytes = outputStream.toByteArray();

ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(bytes));
StarveSingleton instance = (StarveSingleton) inputStream.readObject();
System.out.println(starveSingleton == instance);
}
}

  readResolve方法是ObjectInputStream类进行反序列化的一个特殊机制,这里后续再展开,总之这里实现该方法可以保证单例不被破坏。

总结

  下面是我对这个知识点的心得。如何破坏单例模式,这道题怎么思考呢?单例模式和创建对象相关,我们知道创建对象有4种方式

  1. 直接new
  2. 调用clone方法
  3. 序列化反序列化
  4. 反射

其中方式1已经被单例模式限制住,方式2我们一般很少实现clone接口,所以可以破坏单例模式的方法就从3,4而来。

-------------本文结束感谢您的阅读-------------

本文标题:设计模式-如何防止单例模式被破坏

文章作者:小建儿

发布时间:2018年07月26日 - 19:07

最后更新:2018年08月06日 - 10:08

原始链接:http://yajian.github.io/设计模式-如何防止单例模式被破坏/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。