设计模式之创建型设计模式-单例模式

1. 模式简介

确保一个类在任何情况下都绝对只有一个实例,并提供一个全局的访问入口点;隐藏所有的构造方法;属于创建型设计模式

2. 实现方式

2.1. 饿汉式单例

先将实例创建出来,用的时候直接拿

2.1.1. 属性初始化

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
/**
* <p>
* 饿汉式单例,在属性初始化
* </p>
*
* @author yangkai.shen
* @date Created in 2019-08-11 11:32
*/
public class EagerSingleton1 {
private final static EagerSingleton1 INSTANCE = new EagerSingleton1();

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

/**
* 提供全局访问入口
*/
public static EagerSingleton1 getInstance() {
return INSTANCE;
}

public static void main(String[] args) throws InterruptedException {
ConcurrentExecutor.execute(() -> {
System.out.println("线程号: " + Thread.currentThread().getName() + "," + EagerSingleton1.getInstance());
}, 10, 5);
}

}

2.1.2. 静态代码块初始化

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
/**
* <p>
* 饿汉式单例,在静态代码块初始化
* </p>
*
* @author yangkai.shen
* @date Created in 2019-08-11 11:46
*/
public class EagerSingleton2 {
/**
* 此处需要设置为 {@code final} 的,防止被后续赋值!
*/
private final static EagerSingleton2 INSTANCE;

static {
INSTANCE = new EagerSingleton2();
}

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

/**
* 提供全局访问入口
*/
public static EagerSingleton2 getInstance() {
return INSTANCE;
}

public static void main(String[] args) throws InterruptedException {
ConcurrentExecutor.execute(() -> {
System.out.println("线程号: " + Thread.currentThread().getName() + "," + EagerSingleton2.getInstance());
}, 10, 5);
}
}

2.2. 懒汉式单例

用的时候再去创建

2.2.1. 简单实现

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
/**
* <p>
* 懒汉式单例,简单实现
* </p>
*
* @author yangkai.shen
* @date Created in 2019-08-11 19:15
*/
public class LazySingletonSimple {
private static LazySingletonSimple INSTANCE = null;

private LazySingletonSimple() {
}

/**
* 此处需要加 {@code synchronized},保证线程安全
*/
public synchronized static LazySingletonSimple getInstance() {
if (INSTANCE == null) {
INSTANCE = new LazySingletonSimple();
}
return INSTANCE;
}

public static void main(String[] args) throws InterruptedException {
ConcurrentExecutor.execute(() -> {
System.out.println("线程号: " + Thread.currentThread().getName() + "," + LazySingletonSimple.getInstance());
}, 10, 5);
}
}

2.2.2. 双重检查锁实现

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
/**
* <p>
* 懒汉式单例,双重检查锁
* </p>
*
* @author yangkai.shen
* @date Created in 2019-08-11 19:19
*/
public class LazySingletonDoubleCheck {
/**
* 添加 {@code volatile} 解决底层 CPU 指令重排的问题
*/
private volatile static LazySingletonDoubleCheck INSTANCE = null;

private LazySingletonDoubleCheck() {
}

public static LazySingletonDoubleCheck getInstance() {
if (INSTANCE == null) {
synchronized (LazySingletonDoubleCheck.class) {
if (INSTANCE == null) {
INSTANCE = new LazySingletonDoubleCheck();
}
}
}
return INSTANCE;
}

public static void main(String[] args) throws InterruptedException {
ConcurrentExecutor.execute(() -> {
System.out.println("线程号: " + Thread.currentThread().getName() + "," + LazySingletonDoubleCheck.getInstance());
}, 10, 5);
}
}

2.2.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
32
33
34
35
36
37
38
39
40
/**
* <p>
* 懒汉式单例,内部类写法,{@code 推荐写法之一}
* </p>
*
* @author yangkai.shen
* @date Created in 2019-08-11 19:24
*/
public class LazySingletonInnerClass {
private LazySingletonInnerClass() {
// 反射获取类的构造方法,通过newInstance() 获取对象会存在单例被破坏的问题
// 添加以下代码解决,强制不允许构建
if (SingletonHolder.INSTANCE != null) {
throw new RuntimeException("不允许创建多个实例");
}
}

public static LazySingletonInnerClass getInstance() {
return SingletonHolder.INSTANCE;
}

private static class SingletonHolder {
private final static LazySingletonInnerClass INSTANCE = new LazySingletonInnerClass();
}

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 普通方式获取
LazySingletonInnerClass instance1 = LazySingletonInnerClass.getInstance();

// 反射方式获取
Class<LazySingletonInnerClass> clazz = LazySingletonInnerClass.class;
Constructor<LazySingletonInnerClass> constructor = clazz.getDeclaredConstructor(null);
// 设置访问级别,因为是private的
constructor.setAccessible(true);
LazySingletonInnerClass instance2 = constructor.newInstance();

System.out.println("instance1 = " + instance1);
System.out.println("instance2 = " + instance2);
}
}

2.3. 注册式单例

将每一个实例都缓存到统一的容器中,使用唯一标识获取实例

2.3.1. 枚举式单例(推荐写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* <p>
* 注册式单例,枚举实现,{@code 推荐写法之一}
* </p>
*
* @author yangkai.shen
* @date Created in 2019-08-11 19:35
*/
public enum RegisterSingletonEnum {
/**
* 单例
*/
INSTANCE;

public static RegisterSingletonEnum getInstance() {
return INSTANCE;
}

public static void main(String[] args) throws InterruptedException {
ConcurrentExecutor.execute(() -> {
System.out.println("线程号: " + Thread.currentThread().getName() + "," + RegisterSingletonEnum.getInstance());
}, 10, 5);
}
}

2.3.2. 容器式单例

2.3.2.1. Spring式实现
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
/**
* <p>
* 注册式单例,容器实现,Spring 容器的实现
* </p>
*
* @author yangkai.shen
* @date Created in 2019-08-11 19:38
*/
public class RegisterSingletonContainer {
private static final Map<String, Object> BEAN_CACHE = new ConcurrentHashMap<>();

private RegisterSingletonContainer() {
}

public static Object getInstance(String className) {
synchronized (BEAN_CACHE) {
// 判断缓存是否存在
if (!BEAN_CACHE.containsKey(className)) {
// 构建对象,放在缓存
Class<?> aClass = null;
try {
aClass = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
BEAN_CACHE.put(className, aClass);
return aClass;
} else {
return BEAN_CACHE.get(className);
}
}
}

public static void main(String[] args) throws InterruptedException {
ConcurrentExecutor.execute(() -> {
Object instance = RegisterSingletonContainer.getInstance("com.xkcoding.design.pattern.creational.singleton.register.RegisterSingletonContainer");
System.out.println(System.currentTimeMillis() + " :: " + instance);
}, 10, 5);
}
}
2.3.2.2. ThreadLocal式单例

ThreadLocal式单例其实也属于 容器式单例,伪线程安全,保证线程内部全部唯一,同一线程内线程安全

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
/**
* <p>
* 注册式单例,ThreadLocal实现,伪线程安全,保证线程内部全部唯一,同一线程内线程安全
* </p>
*
* @author yangkai.shen
* @date Created in 2019-08-11 19:55
*/
public class RegisterSingletonThreadLocal {
private static final ThreadLocal<RegisterSingletonThreadLocal> INSTANCE = ThreadLocal.withInitial(RegisterSingletonThreadLocal::new);

private RegisterSingletonThreadLocal() {
}

public static RegisterSingletonThreadLocal getInstance() {
return INSTANCE.get();
}

/**
* 测试可见,同一线程内单例,不同线程间非单例
*/
public static void main(String[] args) throws InterruptedException {
System.out.println("线程号: " + Thread.currentThread().getName() + "," + RegisterSingletonThreadLocal.getInstance());
System.out.println("线程号: " + Thread.currentThread().getName() + "," + RegisterSingletonThreadLocal.getInstance());
System.out.println("线程号: " + Thread.currentThread().getName() + "," + RegisterSingletonThreadLocal.getInstance());
System.out.println("线程号: " + Thread.currentThread().getName() + "," + RegisterSingletonThreadLocal.getInstance());

ConcurrentExecutor.execute(() -> {
System.out.println("线程号: " + Thread.currentThread().getName() + "," + RegisterSingletonThreadLocal.getInstance());
}, 10, 5);
}
}

3. 问题点

3.1. 反序列化破坏单例的情况

测试代码:

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
/**
* <p>
* 饿汉式单例,测试反序列化被破坏的情况
* </p>
*
* @author yangkai.shen
* @date Created in 2019-08-11 20:11
*/
public class EagerSingleton3 implements Serializable {
private final static EagerSingleton3 INSTANCE = new EagerSingleton3();

private EagerSingleton3() {
}

public static EagerSingleton3 getInstance() {
return INSTANCE;
}

/**
* 重写 {@code readResolve} 方法,可解决反序列化单例破坏的场景
*/
private Object readResolve() {
return INSTANCE;
}

public static void main(String[] args) throws IOException, ClassNotFoundException {
// 正常获取实例
EagerSingleton3 instance1 = EagerSingleton3.getInstance();

// 通过序列化获取
EagerSingleton3 instance2 = null;

// 将 instance1 写出到文件
FileOutputStream fos = new FileOutputStream("EagerSingleton3.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance1);
oos.flush();
oos.close();

// 读取文件到 instance2
FileInputStream fis = new FileInputStream("EagerSingleton3.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
instance2 = (EagerSingleton3) ois.readObject();
ois.close();

System.out.println(instance1);
System.out.println(instance2);
System.out.println(instance1 == instance2);
}
}

其实上面的代码已经给出答案了,重写 readResolve() 方法,将 INSTANCE 返回即可解决。

为什么重写 readResolve() 方法,将 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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// 1. 首先得明确 原因应该是反序列化为对象的时候 导致单例失败的
// 所以跟踪一波 ois.readObject() 源码 -> java.io.ObjectInputStream#readObject

// 2. debug 可以发现,执行了 readObject0(false) 返回对象
Object obj = readObject0(false);

// 3. 继续跟踪 readObject0() 的源码 -> java.io.ObjectInputStream#readObject0

// 省略部分代码,只看下面的 switch 部分代码
switch (tc) {
case TC_NULL:
return readNull();

case TC_REFERENCE:
return readHandle(unshared);

case TC_CLASS:
return readClass(unshared);

case TC_CLASSDESC:
case TC_PROXYCLASSDESC:
return readClassDesc(unshared);

case TC_STRING:
case TC_LONGSTRING:
return checkResolve(readString(unshared));

case TC_ARRAY:
return checkResolve(readArray(unshared));

case TC_ENUM:
return checkResolve(readEnum(unshared));

case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));

case TC_EXCEPTION:
IOException ex = readFatalException();
throw new WriteAbortedException("writing aborted", ex);

case TC_BLOCKDATA:
case TC_BLOCKDATALONG:
if (oldMode) {
bin.setBlockDataMode(true);
bin.peek(); // force header read
throw new OptionalDataException(
bin.currentBlockRemaining());
} else {
throw new StreamCorruptedException(
"unexpected block data");
}

case TC_ENDBLOCKDATA:
if (oldMode) {
throw new OptionalDataException(true);
} else {
throw new StreamCorruptedException(
"unexpected end of block data");
}

default:
throw new StreamCorruptedException(
String.format("invalid type code: %02X", tc));
}

// debug可以发现反序列化的时候走的是
case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared));

// 4. 因此,继续跟踪源码 readOrdinaryObject(unshared) -> java.io.ObjectInputStream#readOrdinaryObject

// 省略部分代码
obj = desc.isInstantiable() ? desc.newInstance() : null;
// 这段代码里 desc.isInstantiable() 主要是判断类是否可以被序列化和反序列化,同时是否提供一个无参的构造方法
// 这里显然为 true, 所以 obj = desc.newInstance(),这就意味着已经被新创建了一个对象

// 5. 上面只是解释了为什么反序列化会破坏单例的情况,下面我们看看怎么解决
// 接着看 java.io.ObjectInputStream#readOrdinaryObject 这个方法

// 省略部分代码
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);
}
}
// 这里存在一个 if 的条件判断,desc.hasReadResolveMethod()
// 满足条件的时候,Object rep = desc.invokeReadResolve(obj);
// 如果 rep != obj 的时候呢,就把 rep 赋值给 obj, handles.setObject(passHandle, obj = rep);

// 6. 看到这里,我们就得出一个结论:
// 如果要是仍然保证单例,我们只要让 rep 返回的时候等于原先的对象覆盖给 obj 就可以实现单例了

// 7. 所以这里的关键就是 desc.hasReadResolveMethod() 和 desc.invokeReadResolve(obj) 这两个方法了

// 7.1. 那我们首先来看看 desc.hasReadResolveMethod() 这个方法
boolean hasReadResolveMethod() {
requireInitialized();
return (readResolveMethod != null);
}

// 这里就只是判断 readResolveMethod 这个是否为 null,那我们就来看看 readResolveMethod 这到底是个啥吧
/** class-defined readResolve method, or null if none */
private Method readResolveMethod;

// 属性的声明说的很清楚,是一个名为 readResolve 的方法,但是返回值是啥呢,我们再找找,这个值不会凭空生成,要么是构造方法生成的时候放进去的,要么就是手动set的
// 那我们就用快捷键 Command + B 看看这个属性在哪里被用到了吧
// 不难发现,这是在 java.io.ObjectStreamClass#ObjectStreamClass(java.lang.Class<?>) 构造方法的时候,通过反射设置的

// 省略部分代码
readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);

// 再看 java.io.ObjectStreamClass#getInheritableMethod(Class<?> cl, String name, Class<?>[] argTypes, Class<?> returnType)
// readResolveMethod 就是 函数名为 readResolve,无参,返回值是 Object 的方法

// 7.2. 再看看 desc.invokeReadResolve(obj) 这个方法 -> java.io.ObjectStreamClass#invokeReadResolve
// 这个方法就是反射调用 readResolveMethod 这个 Method 对象返回结果

// 8. 到这儿,聪明的你应该知道怎么玩儿了吧,重写 readResolve 方法,返回 INSTANCE 完事~
private Object readResolve() {
return INSTANCE;
}

注意哦,当我们使用枚举式单例的写法就不会出现这种情况,具体原因如下:

1
2
3
4
5
6
7
// 还是走到 上面的 switch 分支,但是枚举式单例就会走
case TC_ENUM: return checkResolve(readEnum(unshared));

// 跟踪 java.io.ObjectInputStream#readEnum 源码可以发现
// 省略部分代码
Enum<?> en = Enum.valueOf((Class)cl, name);
// 所以不会导致单例被破坏的情况~

3.2. 反射破坏单例的情况

测试代码:

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
/**
* <p>
* 懒汉式单例,内部类写法,{@code 推荐写法之一}
* </p>
*
* @author yangkai.shen
* @date Created in 2019-08-11 19:24
*/
public class LazySingletonInnerClass {
private LazySingletonInnerClass() {
// 反射获取类的构造方法,通过newInstance() 获取对象会存在单例被破坏的问题
// 添加以下代码解决,强制不允许构建
if (SingletonHolder.INSTANCE != null) {
throw new RuntimeException("不允许创建多个实例");
}
}

public static LazySingletonInnerClass getInstance() {
return SingletonHolder.INSTANCE;
}

private static class SingletonHolder {
private final static LazySingletonInnerClass INSTANCE = new LazySingletonInnerClass();
}

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 普通方式获取
LazySingletonInnerClass instance1 = LazySingletonInnerClass.getInstance();

// 反射方式获取
Class<LazySingletonInnerClass> clazz = LazySingletonInnerClass.class;
Constructor<LazySingletonInnerClass> constructor = clazz.getDeclaredConstructor(null);
// 设置访问级别,因为是private的
constructor.setAccessible(true);
LazySingletonInnerClass instance2 = constructor.newInstance();

System.out.println("instance1 = " + instance1);
System.out.println("instance2 = " + instance2);
}
}

其实上面的代码已经给出答案了,只需要在构造方法里判断当前实例是否已创建,已创建抛出运行时异常,即可解决。

4. 应用

1
2
3
4
5
6
7
// 1. ServletContext

// 2. ServletConfig

// 3. ApplicationContext

// 4. DBPool

5. 优缺点

优点: 在内存中只有一个实例,减少内存开销;可以避免对资源的多重占用;设置全局访问点,严格控制访问

缺点: 没有接口,扩展困难;扩展单例对象,只能修改代码,不符合开闭原则

6. 完整代码地址

https://github.com/xkcoding/design-pattern/tree/master/src/main/java/com/xkcoding/design/pattern/creational/singleton

测试代码里 ConcurrentExecutor 具体实现请看这里:https://github.com/xkcoding/design-pattern/blob/master/src/main/java/com/xkcoding/design/pattern/utils/ConcurrentExecutor.java

-------------本文结束  感谢您的阅读-------------
xkcoding wechat
欢迎来我的公众号「xkcoding小凯扣丁」逛逛
o(╯□╰)o 赞助一杯咖啡 ~~