Wetts's blog

Stay Hungry, Stay Foolish.

0%

Java-设计模式-单例模式问题(反射、序列化).md

参考:https://www.cnblogs.com/call-me-pengye/p/11169051.html、https://zhuanlan.zhihu.com/p/144092983

使用枚举(《Effective Java》作者的Josh Bloch提倡的方式)

破环单例模式的方式

破环单例模式的三种方式:反射,序列化,克隆

以双重检测方式为例测试反射,序列化,克隆是否能破环单例模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Singleton  implements Serializable,Cloneable{
private static final long serialVersionUID = 6125990676610180062L;
private static Singleton singleton;

private Singleton(){
}
public void doAction(){
//TODO 实现你需要做的事
}
public static Singleton getInstance(){
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

测试用例:

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
public class DestroySingleton {

public static void main(String[] args) throws Exception {
//通过getInstance()获取
Singleton singleton = Singleton.getInstance();
System.out.println("singleton的hashCode:"+singleton.hashCode());
//通过反射获取
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton reflex = constructor.newInstance();
System.out.println("reflex的hashCode:"+reflex.hashCode());
//通过克隆获取
Singleton clob = (Singleton) Singleton.getInstance().clone();
System.out.println("clob的hashCode:"+clob.hashCode());
//通过序列化,反序列化获取
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(Singleton.getInstance());
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Singleton serialize = (Singleton) ois.readObject();
if (ois != null) ois.close();
if (bis != null) bis.close();
if (oos != null) oos.close();
if (bos != null) bos.close();
System.out.println("serialize的hashCode:"+serialize.hashCode());
}
}

运行结果:

1
2
3
4
singleton的hashCode:366712642
reflex的hashCode:1829164700
clob的hashCode:2018699554
serialize的hashCode:990368553

运行结果表明通过 getInstance()、反射、克隆、序列化这四种方式得到的 Singleton 对象的 hashCode 是不一样的,此时单例模式已然被破环

如何防止反射、克隆、序列化对单例模式的破环

  • 防止反射破环(虽然构造方法已私有化,但通过反射机制使用 newInstance() 方法构造方法也是可以被调用):
    • 首先定义一个全局变量开关 isFristCreate 默认为开启状态
    • 当第一次加载时将其状态更改为关闭状态
  • 防止克隆破环
    • 重写 clone(),直接返回单例对象
  • 防止序列化破环
    • 添加 readResolve(),返回 Object 对象
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
public class Singleton  implements Serializable,Cloneable{
private static final long serialVersionUID = 6125990676610180062L;
private static Singleton singleton;
private static boolean isFristCreate = true;//默认是第一次创建

private Singleton() {
if (isFristCreate) {
synchronized (Singleton.class) {
if (isFristCreate) {
sFristCreate = false;
}
}
} else {
throw new RuntimeException("已然被实例化一次,不能在实例化");
}
}
public void doAction(){
//TODO 实现你需要做的事
}
public static Singleton getInstance(){
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
@Override
protected Singleton clone() throws CloneNotSupportedException {
return singleton;
}
private Object readResolve() {
return singleton;
}
}

测试用例:

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
public class DestroySingleton {

public static void main(String[] args) throws Exception {
//通过getInstance()获取
Singleton singleton = Singleton.getInstance();
System.out.println("singleton的hashCode:"+singleton.hashCode());
//通过克隆获取
Singleton clob = (Singleton) Singleton.getInstance().clone();
System.out.println("clob的hashCode:"+clob.hashCode());
//通过序列化,反序列化获取
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(Singleton.getInstance());
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Singleton serialize = (Singleton) ois.readObject();
if (ois != null) ois.close();
if (bis != null) bis.close();
if (oos != null) oos.close();
if (bos != null) bos.close();
System.out.println("serialize的hashCode:"+serialize.hashCode());
//通过反射获取
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton reflex = constructor.newInstance();
System.out.println("reflex的hashCode:"+reflex.hashCode());
}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
singleton的hashCode:366712642
clob的hashCode:366712642
serialize的hashCode:366712642
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 designPatterns.singleton.doublecheck.DestroySingleton.main(DestroySingleton.java:33)
Caused by: java.lang.RuntimeException: 已然被实例化一次,不能在实例化
at designPatterns.singleton.doublecheck.Singleton.<init>(Singleton.java:16)
... 5 more

从运行结果上看重写 clone(),添加 readResolve() 后通过克隆和序列化得到的对象的 hashCode 与从 getInstance() 得到的对象得而 hashCode 值相同,而通过反射运行得到的结果符合预想的报错;因为以上三种手段对防止单例被破坏起作用了