Java单例模式的实现与破坏

单例模式是一种设计模式,是在整个运行过程中只需要产生一个实例。那么怎样去创建呢,以下提供了几种方案。

一、创建单例对象

懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TestSingleton {

// 构造方法私有化
private TestSingleton(){}

// 声明实例
private static TestSingleton singleton;

// 提供外部调用方法,生成并获取实例
public static TestSingleton getInstance() {
if(singleton == null) {
singleton = new TestSingleton();
}
return singleton;
}
}

此方案是以时间换空间,启动时并不会执行任何操作,只有被调用时,采取实例化对象。不过这种方法在多线程下不安全,因为两个线程如果同时调用时,会同时通过非空验证的验证,造成创建两个对象的后果,有悖设计初衷。

针对多线程问题,应该加入双重非空判断:

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

// 构造方法私有化
private TestSingleton(){}

// 声明实例
private static volatile TestSingleton singleton;

// 提供外部调用方法,生成并获取实例
public static TestSingleton getInstance() {
if(singleton == null) {
synchronized (TestSingleton.class) {
if(singleton == null) {
singleton = new TestSingleton();
}
}
}
return singleton;
}
}

饿汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestSingleton {

// 构造方法私有化
private TestSingleton(){}

// 声明并生成实例
private static TestSingleton singleton = new TestSingleton();

// 提供外部调用方法,获取实例
public static TestSingleton getInstance() {
return singleton;
}
}

以空间换时间,类一加载时,就对其进行实例化,后面调用时直接提供对象实例。

静态内部类实现懒加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestSingleton {

// 构造方法私有化
private TestSingleton(){}

private static class TestSingletonFactory{
private static TestSingleton singleton = new TestSingleton();
}

// 提供外部调用方法,获取实例
public static TestSingleton getInstance() {
return TestSingletonFactory.singleton;
}
}

当getInstance方法被调用时,才会初始化静态内部类TestSingletonFactory的静态变量singleton。此处由JVM来保障线程安全。

二、破坏单例

实现单例后,按照预期结果应该所有对象都是同一个对象。但是以下有几种情况可以破坏单例的性质。

首先让单例类实现Serializable, Cloneable接口,以便实验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TestSingleton implements Serializable, Cloneable{

private static final long serialVersionUID = 1L;

// 构造方法私有化
private TestSingleton(){}

private static class TestSingletonFactory{
private static TestSingleton singleton = new TestSingleton();
}

// 提供外部调用方法,获取实例
public static TestSingleton getInstance() {
return TestSingletonFactory.singleton;
}
}
  • 序列化
1
2
3
4
5
6
7
8
9
10
11
12
// 获取实例
TestSingleton originSingleton = TestSingleton.getInstance();
// 写出对象
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(originSingleton);
// 写入对象
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
TestSingleton serializeSingleton = (TestSingleton) ois.readObject();
// 判断两个对象是否相等
System.out.println(originSingleton == serializeSingleton); // false
  • 反射
1
2
3
4
5
6
7
8
9
10
// 反射
Class<TestSingleton> clazz = TestSingleton.class;
// 获取无参构造函数
Constructor<TestSingleton> constructor = clazz.getDeclaredConstructor();
// 将私有设置为可见
constructor.setAccessible(true);
// 用构造器生成实例
TestSingleton instance = constructor.newInstance();
// 判断两个对象是否相等
System.out.println(originSingleton == instance); // false
  • 克隆
1
2
3
// 克隆
TestSingleton clone = (TestSingleton) originSingleton.clone();
System.out.println(originSingleton == clone); // false

三、修复破坏

对于这种预料之外的结果,我们应该怎样去控制呢?

  • 序列化

添加readResolve方法,返回Object。

  • 反射

添加全局可见变量,如果再次调用构造方法生成实例时,抛出运行时错误。

  • 克隆

重写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
29
30
31
32
33
34
35
36
public class TestSingleton implements Serializable, Cloneable{

private static final long serialVersionUID = 1L;

private static volatile boolean isCreated = false;//默认是第一次创建

// 构造方法私有化
private TestSingleton(){
if(isCreated) {
throw new RuntimeException("实例已经被创建");
}
isCreated = true;
}

private static class TestSingletonFactory{
private static TestSingleton singleton = new TestSingleton();
}

// 提供外部调用方法,获取实例
public static TestSingleton getInstance() {
return TestSingletonFactory.singleton;
}

@Override
protected Object clone() throws CloneNotSupportedException {
return getInstance();
}

/**
* 防止序列化破环
* @return
*/
private Object readResolve() {
return getInstance();
}
}
查看评论