bboyjing's blog

HeadFirst设计模式五【单例模式】

单例模式是用来创建只能有一个实例对象的模式,该模式虽然只有一个类,但却并没有想象中那么容易,下面我们就一起来学习下。

场景描述

单例模式的类图上只有一个类,场景很简单,就是有一个类,让它只能实例化。

代码示例

下面会列出几个版本的代码示例,其中有的版本是有问题的,最终逐步会实现成最终正确的版本。

第一个有问题的版本

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
public class Singleton {
/**
* 利用一个静态变量类记录唯一实例
*/
private static Singleton uniqueInstance;
/**
* 私有构造函数,只有Singleton类内才可以调用
*/
private Singleton() {
}
/**
* 实例化对象,并返回这个实例
* 是有在调用该方法是才实例化,所以实现了延迟加载
*
* @return 实例
*/
public static Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}

该版本的实现有一个隐藏的问题,就是当多个线程同时获取实例的时候,有可能会创建出多个实例。因为getInstance()方法并没有加锁,当两个线程同时跑到uniqueInstance == null时,两者都判断为空,就会创建出两个不一样的实例。

加同步锁的版本

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SynchronizedSingleton {
private static SynchronizedSingleton uniqueInstance;
private SynchronizedSingleton() {
}
public static synchronized SynchronizedSingleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new SynchronizedSingleton();
}
return uniqueInstance;
}
}

这个版本只是简单地在getInstance()方法上添加了synchronized,来限制同时只有一个线程访问该方法。虽然同步版本解决了可能会创建多个实例的问题,但同时带来了额外的锁性能开销。更严重的是,其实只有第一次执行此方法时,才需要同步,之后每次调用,同步都是累赘。

预实例化版本

1
2
3
4
5
6
7
8
9
10
public class DirectSingleton {
private static DirectSingleton uniqueInstance = new DirectSingleton();
private DirectSingleton() {
}
public static DirectSingleton getInstance() {
return uniqueInstance;
}
}

这个版本也是简单粗暴,不管用不用,在JVM加载这个类时马上实例化了一个对象。JVM保证在任何线程访问uniqueInstance之前,一定已经实例化了。这个版本的缺点就是放弃了延迟初始化。

双重检查加锁版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class DoubleCheckSingleton {
private volatile static DoubleCheckSingleton uniqueInstance;
private DoubleCheckSingleton() {
}
public static DoubleCheckSingleton getInstance() {
if (uniqueInstance == null) {
synchronized (DoubleCheckSingleton.class) {
uniqueInstance = new DoubleCheckSingleton();
}
}
return uniqueInstance;
}
}

这个版本利用了volatile多线程之间的可见性,以及局部代码加锁,解决了性能问题,是目前最优的单例模式版本了。但是还能再进一步,我们往下看。

静态内部类方式

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 InnerClassSingleton {
/**
* 私有构造函数
*/
private InnerClassSingleton() {
}
/**
* 静态内部类,由于静态内部类只会被JVM加载一次,所以线程安全
*/
private static class InnerSingleton {
private static final InnerClassSingleton UNIQUE_SINGLETON = new InnerClassSingleton();
}
/**
* 静态内部类,保证线程安全
* 而且是延迟加载
*
* @return 实例
*/
public static InnerClassSingleton getInstance() {
return InnerSingleton.UNIQUE_SINGLETON;
}
}

利用静态内部类机制来初始化实例,静态内部类只有当被调用的时候才开始被JVM加载,而且JVM的加载保证了线程安全。

上面实现的四种正确的版本中,个人偏向于最后一种。因为有延迟加载,线程安全且没有锁开销,代码实现也比较优雅。