单例模式

单例模式

定义

保证一个类仅有一个实例,并提供一个全局访问点。

类型

创建型

适用场景

1
2
想确保任何情况下都绝对只有一个实例
- 数据库连接池、线程池以及计数器等

优点

1
2
3
4
5
6
1. 在内存里只有一个实例,减少了内存开销
- 特别是一个对象频繁的创建和销毁,而且在创建和销毁时性能又不能很好的优化
2. 可以避免对资源的多重占用
- 如对一个文件进行写操作,由于只有一个实例存在内存中,可以避免对同一个资源文件同时写操作
3. 设置全局访问点,严格控制访问
- 对外控制创建的入口

缺点

1
没有接口,扩展困难,想要扩展需要修改源代码

拓展点

1
2
3
4
5
6
7
8
9
10
11
12
1. 私有构造器
- 为了禁止从单例类外部调用构造函数创建对象,为了达到目的必须设置构造函数为私有的
2. 线程安全
- 线程安全在单例模式设计的过程中非常重要
3. 延迟加载
- 延时创建对象
4. 序列化和反序列化安全
- 对于单例对象一旦序列化和反序列化,就会对单例进行破坏
5. 反射
- 单例模式也要防止反射攻击
6. 双重检锁机制
7. 单例静态内部类的实现方案

单例模式相关的设计模式

单例模式和工厂模式

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
32
33
34
35
36
37
38
39
40
41
package com.design.pattern.singleton.lazynosafe;

/**
* LazyDoubleCheckSingleton 懒汉式-线程不安全
*
* @author shunhua
* @date 2019-10-02
*/
public class LazySingleton {
/**
* 定义LazySingleton属性
*/
private static LazySingleton lazySingleton = null;

/**
* 指定构造方法是私有的
*/
private LazySingleton(){}

/**
* 全局控制点
* @return
*/
public static LazySingleton getInstance(){
/**
* 在没有断点干预的情况下,多线程执行和CPU分配有关。为了更清楚的观看多线程执行,可以使用多线程debug来达到控制多个线程的目的
*
* 在多线程下有以下几种可能,这里以两个线程解释,线程A和线程B
*
* 1 当线程B走到if(lazySingleton == null)时,线程A已经执行创建好了对象,此时线程B直接返回线程A创建的对象
* 2 当线程B走到if(lazySingleton == null)时,线程A还没有创建好对象即LazySingleton仍然为空,紧接着线程B的if判断通过,当A创建完对象准备返回lazySingleton即执行return lazySingleton时,线程B创建好了对象并赋值给lazySingleton,此时lazySingleton变量的值是线程B创建的对象引用,会覆盖线程A创建的对象对应的引用,最终线程A和线程B返回的虽然是指向同一个对象(线程B创建的)的引用,但是实质上对象已经创建了两次。
* 3 当线程B走到if(lazySingleton == null)时,线程A还没有创建好对象即LazySingleton仍然为空,仅接着线程B的if判断通过,线程A在线程B创建对象之前返回了,那么最终线程A和线程B都会创建对象,并且返回的对象引用不会相同,它们指向各自创建的对象。
*
*/
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.design.pattern.singleton.lazynosafe;

import lombok.extern.slf4j.Slf4j;

/**
* MyThread 实现Runnable接口,实现多线程
*
* @author shunhua
* @date 2019-10-02
*/
@Slf4j
public class MyThread implements Runnable {

@Override
public void run() {
// 获取目标对象
LazySingleton lazySingleton = LazySingleton.getInstance();
// 打印当前执行的线程信息和目标对象信息
log.info(Thread.currentThread().getName() +" "+ lazySingleton);
}
}

懒汉式单例模式安全-锁粒度较大

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
package com.design.pattern.singleton.lazysafebutlockisbig;

/**
* LazyDoubleCheckSingleton 懒汉式-线程安全但是锁的粒度太大
*
* @author shunhua
* @date 2019-10-02
*/
public class LazySingleton {
/**
* 定义LazySingleton属性
*/
private static LazySingleton lazySingleton = null;

/**
* 指定构造方法是私有的
*/
private LazySingleton(){}

/**
* 全局控制点
*
* synchronize加锁的位置不同,线程持有的对象也会不同
* 1 加在静态方法上,持有的是类的class文件,即当前类
* 2 加在非静态方法上,持有的是堆内存中的对象,即执行当前方法的对象
*
* @return
*/
public synchronized static LazySingleton getInstance(){
/**
* synchronize加锁在静态方法上等同于锁代码块时LazySingleton.class作为持有对象:
*
* public static LazyDoubleCheckSingleton getInstance(){
* synchronized(LazyDoubleCheckSingleton.class){
* if (lazySingleton == null) {
* lazySingleton = new LazyDoubleCheckSingleton();
* }
* }
* return lazySingleton;
* }
*/
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}

}

懒汉式单例模式-双重检锁

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
package com.design.pattern.singleton.lazysafedoublecheck;

/**
* LazyDoubleCheckSingleton 双重检锁,兼顾了性能和线程安全
*
* @author shunhua
* @date 2019-10-03
*/
public class LazyDoubleCheckSingleton {

/**
* 定义LazySingleton属性 ,这里加volatile关键字防止指令重排序和内存可见
*/
private volatile static LazyDoubleCheckSingleton lazySingleton = null;

/**
* 指定构造方法是私有的
*/
private LazyDoubleCheckSingleton(){}

/**
* 全局控制点
*
* 双重检锁指的就是两次判断
*
* @return
*/
public static LazyDoubleCheckSingleton getInstance(){
/**
* 1 这一层if判断如果不使用也能保证线程安全,但是锁的粒度又回到了大粒度版本。使用这一层判断是为了缩小synchronized锁的粒度
* 2 引入了这一层会增加一个隐患-由于指令重排序,走到该层if判断lazySingleton可能确实不为空,但是它指向的对象可能还没有初始化完成,当使用这个对象的时候可能会导致系统异常
*/
if(lazySingleton == null){
/**
* 加锁
*/
synchronized (LazyDoubleCheckSingleton.class){
/**
* 这一层if必须要有,因为synchronized锁的粒度小了,不是整个方法,当出现线程进入第一个if块中但被阻塞在同步代码块外时(别的线程拿到了锁在里面创建对象),
* 如果不加该if判断该线程还会创建一个对象,而不会直接返回已经创建好的对象的引用。
*/
if(lazySingleton == null){
/** LazyDoubleCheckSingleton = new LazyDoubleCheckSingleton() JVM主要做的事粗略步骤如下:
*
* 1. 在堆空间里分配内存给这个对象
* 2. 执行构造方法进行初始化,注意此初始化不是类加载过程中的初始化
* 3. 设置lazySingleton指向分配好的内存地址
* 注意:在没有处理指令重排序的情况下2、3两步由于重排序可能步骤会倒置(因为Java语言规范,允许那些在单线程内不会改变单线程程序执行结果的重排序,因为有的重排序可以提高程序执行性能 ),这会可能会造成线程拿到的引用指向的是一个还没有初始化完成的对象,虽然不为空但它还没有执行构造方法,如果恰巧构造方法里面需要对某些参数进行初始化,当使用这个对象还没有初始化的参数时会导致系统异常
*
* 详细的步骤如下:
* 1. 当遇到new指令时,会先检查这个指定的参数也就是LazyDoubleCheckSingleton能否在常量池中定位到该类的符号引用,并且检查这个符号引用代表的类是否已经执行过类的加载(加载、解析、准备和初始化),如果没有就执行下一步,如果执行了接着虚拟机为新生对象分配内存(此时从虚拟机的视角来说一个新对象已经产生了),紧接着执行new指令执行之后的调用<init>方法。
* 2. 加载 (1 通过类的全限定名获取定义此类的二进制字节流 2 将这个字节流代表的静态存储结构转化为方法区的运行时数据结构即Class中的常量池进入方法区的运行时常量池中 3 在方法区生成一个代表这个类的Class对象)
* 3. 验证 (确保Class文件的字节流中包含的信息符合当前虚拟机的要求,确保虚拟机自身安全)
* 4. 准备 (在方法区中为类变量分配内存并设置类变量初始值)
* 5. 解析 (可能会发生,因为它也可能发生在初始化阶段之后。 主要作用就是将常量池中的符号引用替换为直接引用)
* 6. 初始化 (这是类加载过程的最后一步,主要就是执行类构造器<clinit>方法,初始化类变量)
*
* 最后:设置lazySingleton指向分配好的内存地址
*/
lazySingleton = new LazyDoubleCheckSingleton();
}
}

}
return lazySingleton;
}

}

枚举式单例模式

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
package com.design.pattern.singleton.enuminstance;

/**
* EnumInstance 使用枚举的方式实现单例模式
*
* 1. 枚举实际上是一个继承Enum的被final修饰的类,它的构造方法(只有有参构造方法)也是私有的。其中枚举常量也是static final的,并且在static代码块中实例化(和恶汉式很像)
* 2. 枚举实现的单例防止了序列化攻击(readObject方法执行获取的对象是已经存在的枚举常量)和反射攻击(枚举类的构造函数会判断,如果通过反射调用就抛出异常)以及线程安全
* 3. 推荐使用枚举实现单例
*
* @author shunhua
* @date 2019-10-03
*/
public enum EnumInstance {

/**
* 枚举常量
*/
INSTANCE;
/**
* 枚举的成员变量
*/
private Object data;

public Object getData() {
return data;
}

public void setData(Object data) {
this.data = data;
}

/**
* 暴露给外部的全局点
*
* @return
*/
public static EnumInstance getInstance() {
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
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
package com.design.pattern.singleton.enuminstance;

import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.lang.reflect.Constructor;

/**
* EnumInstanceTest
*
* @author shunhua
* @date 2019-10-03
*/
@Slf4j
public class EnumInstanceTest {

public static void main(String[] args) {

EnumInstance instance = EnumInstance.getInstance();
instance.setData(new Object());

try {
//------------------ 枚举实现的单例,是不受序列化破坏的影响---------------------/

File file = new File("singleton");

// 使用ObjectOutputStream对象输出流,把单例对象写入文件中。注意文件的后缀名带不带都行。如果不指定文件的路径,就默认使用当前工程的目录作为路径
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(file));
// 将单例对象写入文件中
objectOutputStream.writeObject(instance);

// 使用ObjectInputStream对象输入流,把文件中的单例对象读到内存中
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
EnumInstance newInstance = (EnumInstance) objectInputStream.readObject();

/**
readObject方法调用的核心方法:这个逻辑保证了取出的枚举对象的唯一性

private Enum<?> readEnum(boolean unshared) throws IOException {
if (bin.readByte() != TC_ENUM) {
throw new InternalError();
}

ObjectStreamClass desc = readClassDesc(false);
if (!desc.isEnum()) {
throw new InvalidClassException("non-enum class: " + desc);
}

int enumHandle = handles.assign(unshared ? unsharedMarker : null);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(enumHandle, resolveEx);
}
// 获取枚举对象的名称,这个是唯一的,它对应一个枚举常量
String name = readString(false);
Enum<?> result = null;
// 获取枚举对象的Class
Class<?> cl = desc.forClass();
if (cl != null) {
try {
// 通过枚举对象的Class和枚举对象的名称获取对应的枚举常量,没有创建新的对象
@SuppressWarnings("unchecked")
Enum<?> en = Enum.valueOf((Class)cl, name);
result = en;
} catch (IllegalArgumentException ex) {
throw (IOException) new InvalidObjectException(
"enum constant " + name + " does not exist in " +
cl).initCause(ex);
}
if (!unshared) {
handles.setObject(enumHandle, result);
}
}

handles.finish(enumHandle);
passHandle = enumHandle;
return result;
}
*/

log.info("instance: " + instance);
log.info("newInstance: " + newInstance);
log.info(String.format("instance [%s] newInstance", instance == newInstance));

System.out.println("------------------------------------------");

log.info("instance: " + instance.getData());
log.info("newInstance: " + newInstance.getData());
log.info(String.format("instance.data [%s] newInstance.data", instance.getData() == newInstance.getData()));

//-------------------------- 枚举实现的单例,不受反射破坏的影响, ----------------/

Class enumClass = EnumInstance.class;
Constructor constructor = enumClass.getDeclaredConstructor();
constructor.setAccessible(true);
/**
* 通过反射调用枚举的构造器(构造器只有有参构造)会抛出异常,防止了反射攻击
*/

} catch (Exception e) {
log.info(e.getMessage());
}
}

}

容器单例模式

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.design.pattern.singleton.containersingleton;


import org.apache.commons.lang3.StringUtils;
import org.springframework.util.ObjectUtils;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* ContainerSingleton 容器单例模式
*
* 统一管理多个实例,节省资源
*
* @author shunhua
* @date 2019-10-03
*/
public class ContainerSingleton {
/**
* 存放对象,相当于一个缓存
*/
private static Map<String,Object> SINGLETON_MAP = new ConcurrentHashMap<>();

/**
* 多线程情况下不安全,可能导致值的覆盖
* @param key
* @param instance
*/
public static void putInstance(String key,Object instance){
if(StringUtils.isNotBlank(key) && !ObjectUtils.isEmpty(instance)){
if(!SINGLETON_MAP.containsKey(key)){
SINGLETON_MAP.put(key,instance);
}
}
}

/**
* 获取对象
* @param key
* @return
*/
public static Object getInstance(String key){
return SINGLETON_MAP.get(key);
}
}

饿汉式单例模式

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
package com.design.pattern.singleton.hungry;

/**
* HungrySingleton 饿汉式
* <p>
* 1 优点
* 写法简单,类加载(严格来说是类加载过程的初始化阶段以及调用构造函数)的时候就完成了对象的创建,避免了线程同步问题
* 2 缺点
* 类加载的时候就完成了对象的创建,没有延迟效果,如果类的对象从始至终都没有用过,或者只是想获取类的某个类变量,那么还是会创建对象,这无疑造成了内存的浪费
*
*
* @author shunhua
* @date 2019-10-03
*/
public class HungrySingleton {

/**
* 私有构造函数
*/
private HungrySingleton() {
}

/**
* 声明为final的变量必须在类加载完成时(准确的是类加载初始化的时候,singleton就需要被赋值即HungrySingleton对象的引用)就已经赋值
*/
// private final static HungrySingleton singleton = new HungrySingleton();

private final static HungrySingleton singleton;
static {
singleton = new HungrySingleton();
}

/**
* 全局访问点
* @return
*/
public static HungrySingleton getInstance() {
return singleton;
}

}

破坏饿汉式单例-方式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
package com.design.pattern.singleton.reflectattackresolve;

/**
* HungrySingleton 饿汉式
* <p>
* 1 优点
* 写法简单,类加载(严格来说是类加载过程的初始化阶段以及调用构造函数)的时候就完成了对象的创建,避免了线程同步问题
* 2 缺点
* 类加载的时候就完成了对象的创建,没有延迟效果,如果类的对象从始至终都没有用过,或者只是想获取类的某个类变量,那么还是会创建对象,这无疑造成了内存的浪费
*
*
* @author shunhua
* @date 2019-10-03
*/
public class HungrySingleton {

// private final static HungrySingleton singleton = new HungrySingleton();

private final static HungrySingleton singleton;

/**
* 私有构造函数
*/
private HungrySingleton() {
if(singleton == null){
System.out.println("调用构造方法在赋值引用给singleton之前,这是指令重排序带来的可能");
}else {
System.out.println("调用构造方法在赋值引用给singleton之后,这是指令重排序带来的可能");
}
}

/**
* 声明为final的变量必须在类加载完成时(准确的是类加载初始化的时候,singleton就需要被赋值即HungrySingleton对象的引用)就已经赋值
*/

static {
singleton = new HungrySingleton();
}

/**
* 全局访问点
* @return
*/
public static HungrySingleton getInstance() {
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.design.pattern.singleton.reflectattackresolve;


import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Constructor;

/**
* HungrySingletonTest
*
* 反射破坏单例模式不容易彻底阻止,没有特别好的方式。
*
* @author shunhua
* @date 2019-10-03
*/
@Slf4j
public class HungrySingletonTest {

/**
* 对于在类加载的整个过程实例就能创建好的单例模式(恶汉式、静态内部类),为了防止反射攻击,可以在构造方法中进行判断,如果是通过反射创建对象就抛出异常
*
*
* @param args
*/
public static void main(String[] args) {

try {
// 获取hungrySingleton的Class对象
Class objectClass = HungrySingleton.class;

// 通过全局访问点拿到单例对象
HungrySingleton instance = HungrySingleton.getInstance();

// 获取声明的构造器
Constructor constructor = objectClass.getDeclaredConstructor();
// 强制设置声明的构造器是可以访问的
constructor.setAccessible(true);
// 通过构造器反射创建对象
HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();

log.info("instance: " + instance);
log.info("newInstance: " + newInstance);
log.info(String.format("instance [%s] newInstance",instance == newInstance));

}catch (Exception e){
log.info(e.getMessage());
}
}
}

破坏饿汉式单例-方式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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.design.pattern.singleton.serializationdestroysingleton;

import java.io.Serializable;

/**
* HungrySingleton 饿汉式
* <p>
* 1 优点
* 写法简单,类加载(严格来说是类加载过程的初始化阶段以及调用构造函数)的时候就完成了对象的创建,避免了线程同步问题
* 2 缺点
* 类加载的时候就完成了对象的创建,没有延迟效果,如果类的对象从始至终都没有用过,或者只是想获取类的某个类变量,那么还是会创建对象,这无疑造成了内存的浪费
* 3 实现Serializable,为了实现序列化
*
* @author shunhua
* @date 2019-10-03
*/
public class HungrySingleton implements Serializable {
private static final long serialVersionUID = 1136799709809340054L;

/**
* 私有构造函数
*/
private HungrySingleton() {
}

/**
* 声明为final的变量必须在类加载完成时(准确的是类加载初始化的时候,singleton就需要被赋值即HungrySingleton对象的引用)就已经赋值
*/
// private final static HungrySingleton singleton = new HungrySingleton();

private final static HungrySingleton singleton;
static {
singleton = new HungrySingleton();
}

/**
* 全局访问点
* @return
*/
public static HungrySingleton getInstance() {
return singleton;
}

/**
* 1. 对于使用序列化和反序列化产生新的实例的方式破坏了单例,可以在类中增加readResolve()方法来预防,readResolve()方法返回单例对象即可
* 2. 这是反序列化机制决定的,在反序列化的时候会判断类如果实现了Serializable或者Externalizable接口又包含readResolve()方法的话,会直接
* 调用readResolve()方法来获取实例。值得注意的是,readObject方法底层会先通过反射创建一个新的单例实例,然后再通过反射调用readResolve方
* 法获取单例对象。即虽然最后通过readResolve拿到的是已经创建好的对象,但本质上还是通过反射创建了一个新的对象,只是这个新的对象是用来调用readResolve方法
* 返回单例对象而已。
*
*
* @return
*/
public 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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.design.pattern.singleton.serializationdestroysingleton;
import lombok.extern.slf4j.Slf4j;

import java.io.*;

/**
* HungrySingletonTest
*
* @author shunhua
* @date 2019-10-03
*/
@Slf4j
public class HungrySingletonTest {

public static void main(String[] args) {
HungrySingleton instance = HungrySingleton.getInstance();
try {

File file = new File("singleton");

// 使用ObjectOutputStream对象输出流,把单例对象写入文件中。注意文件的后缀名带不带都行。如果不指定文件的路径,就默认使用当前工程的目录作为路径
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(file));
// 将单例对象写入文件中
objectOutputStream.writeObject(instance);

// 使用ObjectInputStream对象输入流,把文件中的单例对象读到内存中
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));

/**
*
* 如果HungrySingleton类实现了Serializable或者Externalizable接口,那么readObject方法底层会使用反射,调用ObjectStreamClass#newInstance方法创建一个新的单例对象,
* 这个单例对象是为了调用它对应的类中的readResolve方法,如果没有实现那两个接口中的任何一个就会返回null。即接着会判断这个新创建的单例对象中有没有readResolve方法,如果
* 有就会通过反射调用这个readResolve方法,最终readObject方法返回的是readResolve方法返回的对象
*
*/

HungrySingleton newInstance = (HungrySingleton) objectInputStream.readObject();

log.info("instance: " + instance);
log.info("newInstance: " + newInstance);
log.info(String.format("instance [%s] newInstance", instance == newInstance));

} catch (Exception e) {
}
}
}

静态内部类的单例模式

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
package com.design.pattern.singleton.staticinnerclass;

/**
* StaticInnerClassSingleton 静态内部类的单例模式 ,使用静态内部类也是做延迟初始化单例对象,来降低单例实例的开销即在需要的时候才进行初始化
*
* @author shunhua
* @date 2019-10-03
*/
public class StaticInnerClassSingleton {

/**
* 私有构造器不能少,防止外部创建对象,让外部只能通过全局访问点拿到单例对象
*/
private StaticInnerClassSingleton(){}

/**
* 1. 这个静态内部类要声明为私有的,因为创建对象在它里面,不能让外面访问它
* 2. 如果类没有初始化,需要类立即初始化的常见情况:
* (1)new 一个对象
* (2)类中声明的静态方法被调用
* (3)类中声明的一个静态成员被赋值
* (4)类中声明的一个静态成员被使用,并且这个成员不是一个常量(被final修饰,已在编译期把结果放入常量池中的静态字段)
* (5)对类进行反射调用
* (6)作为父类(包括接口),其子类被初始化了,那么父类需要先初始化
* (7)执行的主类(包含main方法的类)
*
* 3.使用静态内部类创建单例对象利用了类加载过程中的初始化阶段的特性:
* 虚拟机会保证一个类的类构造器<clinit>方法在多线程环境中被正确地加类的对象初始化锁(这是JVM帮我们自动完成的)、同步,如果多个线程同时去初始化一个类,
* 那么只会有一个线程去执行这个类的类构造器方法<clinit>,其他线程都需要阻塞等待,直到活动线程执行<clinit>方法完毕。
* 因此,即使在多线程环境下执行 private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton()语句
* 也不需要关心指令重排序的情况,因为初始化阶段在对类变量赋值的时候只会有一个线程可以执行<clinit>方法,而单线程执行的情况下,指令是否重排序是没有影响的。
*/
private static class InnerClass {
/**
* 1. 初始化时,需要staticInnerClassSingleton赋值,即 new StaticInnerClassSingleton()会被执行。这些都是<clinit>方法执行的结果,而<clinit>方法在多线程环境下只会有一个线程执行,即使这个方法内部涉及重排序也关系。
* 2. 活跃线程初始化类(执行<clinit>方法)后,类已经初始化完成,不会再进行初始化,其他线程直接访问类成员即可
*/
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}

/**
* 全局访问点
*
* 当执行getInstance方法时就去调用InnerClass内部类里面的staticInnerClassSingleton实例,此时InnerClass内部类会被加载到内存里,在类加载的时候就创建对象,和饿汉式一个道理,保证了只有一个实例,
* 而且在调用getInstance方法时才进行单例的创建,又具有懒汉式的部分特性。
* @return
*/
public static StaticInnerClassSingleton getInstance() {
/**
* 外部访问getInstance这个全局访问点时,会间接访问InnerClass的静态成员,这会导致静态内部类被初始化
*/
return InnerClass.staticInnerClassSingleton;
}

}

单例模式在源码中的使用

jdk-RunTime

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
package java.lang;

import java.io.*;
import java.util.StringTokenizer;
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;

/**
* Every Java application has a single instance of class
* <code>Runtime</code> that allows the application to interface with
* the environment in which the application is running. The current
* runtime can be obtained from the <code>getRuntime</code> method.
* <p>
* An application cannot create its own instance of this class.
*
* @author unascribed
* @see java.lang.Runtime#getRuntime()
* @since JDK1.0
*/

public class Runtime {
private static Runtime currentRuntime = new Runtime();

public static Runtime getRuntime() {
return currentRuntime;
}

private Runtime() {}

// ... 省略其他方法
}

Spring-AbstractFactoryBean

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

@Override
public final T getObject() throws Exception {
if (isSingleton()) {
return (this.initialized ? this.singletonInstance : getEarlySingletonInstance());
}
else {
return createInstance();
}
}

/**
* Determine an 'early singleton' instance, exposed in case of a
* circular reference. Not called in a non-circular scenario.
*/
@SuppressWarnings("unchecked")
private T getEarlySingletonInstance() throws Exception {
Class<?>[] ifcs = getEarlySingletonInterfaces();
if (ifcs == null) {
throw new FactoryBeanNotInitializedException(
getClass().getName() + " does not support circular references");
}
if (this.earlySingletonInstance == null) {
this.earlySingletonInstance = (T) Proxy.newProxyInstance(
this.beanClassLoader, ifcs, new EarlySingletonInvocationHandler());
}
return this.earlySingletonInstance;
}

MyBatis-ErrorContext

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 ErrorContext {

private static final String LINE_SEPARATOR = System.getProperty("line.separator","\n");

// 基于ThreadLocal的单例模式,它不是整个应用全局唯一而是线程级别唯一,保证了每个线程各自的错误上下文
private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<>();

private ErrorContext stored;
private String resource;
private String activity;
private String object;
private String message;
private String sql;
private Throwable cause;
/**
* 私有构造器
*/
private ErrorContext() {
}

/**
* 每个线程获取各自的对象
* @return
*/
public static ErrorContext instance() {
ErrorContext context = LOCAL.get();
if (context == null) {
context = new ErrorContext();
LOCAL.set(context);
}
return context;
}
// ... 等等
}