设计模式之单例模式
# 设计模式之单例模式
# 一、简介
保证一个类仅有一个实例,并提供一个全局访问点
私有的静态成员变量、私有的构造方法、静态的访问实例方法。
# 二、实现方式
# 1、懒汉式,线程安全
它使用了synchronized
关键字,但是效率低,因为99%的情况下不需要同步
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2
3
4
5
6
7
8
9
10
11
# 2、饿汉式
它基于类加载机制避免了多线程的同步问题
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
2
3
4
5
6
7
8
# 3、双检锁/双重校验锁(DCL,即 double-checked locking)
它采用了双锁机制,另外还使用了volatile
和 synchronized
,安全并且在多线程情况下也能够保持高性能
public class Singleton {
private volatile static Singleton singleton;
private Singleton() {
}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {//避免多线程情况下创建多个实例问题
singleton = new Singleton();
}
}
}
return singleton;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
此处需要注意使用volatile
,因为对象的创建并非原子的,可能指令重排 。
instance = new Singleton();
我们可以分为3步完成(伪代码)
memory = allocate();//1.分配对象内存空间
instance(memory);//2.初始化对象
instance = memory;//3.instance指向刚分配的内存地址,此时instance!=null
2
3
由于步骤2和步骤3间可能会重排序,会导致使用了一个未初始化的对象,如下:
memory=allocate();//1.分配对象内存空间
instance=memory;//3.instance指向刚分配的内存地址,此时instance!=null,但是对象还没有初始化完成!
instance(memory);//2.初始化对象
2
3
由于步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变,因此这种重排优化是允许的。但是指令重排只会保证串行语义的执行的一致性(单线程),但并不会关心多线程间的语义一致性。所以当一条线程访问instance不为null时,由于instance实例未必已初始化完成,也就造成了线程安全问题。那么该如何解决呢,很简单,我们使用volatile
禁止instance
变量被执行指令重排优化即可。
//禁止指令重排优化
private volatile static Singleton singleton;
2
# 4、静态内部类
它基于类加载机制,只有通过显式调用 getInstance
方法时,才会开始装载 SingletonHolder
,从而实例化 instance
public class Singleton {
private static class SingletonHolder {//静态内部类
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {
}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
2
3
4
5
6
7
8
9
10
# 5、枚举
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。 这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。 不能通过 reflection attack 来调用私有构造方法。
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
2
3
4
5
# 三、应用
# 1、Spring中的单例模式
Spring中的单例bean使用了单例模式,采用了双重校验锁
/**
* 单例对象的缓存,一级缓存
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//首先通过名字查找这个单例bean存在不存在
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//查看缓存中是否存在这个bean实例
singletonObject = this.earlySingletonObjects.get(beanName);
//如果这个时候的bean实例还是为空并且允许懒加载
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25