0%

Java单例模式

前言

大道至简,单例模式说简单也简单,说复杂也复杂。今天我们就来说说Java的单例模式。

单例模式

最简单的单例

如果你是个Java程序员,我想你闭着眼睛也能写出如下代码:

1
2
3
4
5
6
7
8
public class Singleton {
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
private Singleton() {
}
}

这可以说是最简单的单例了,类的实例化由JVM来完成,因此可以保证线程安全。因此访问效率很高。缺点是不能懒加载,如果不调用getInstance()方法,就会白白占用内存。懒汉式单例模式也因此而提出。

懒汉式

明白了恶汉式的弊端,你很快就能写出懒汉式代码:

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {
// 懒汉式
private static Singleton instance = null;
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
private Singleton() {
}
}

虽然懒汉式单例可以实现懒加载,但是这样也是有代价的,为了保证实例化时线程安全,加入了同步synchronized,增加了访问开销。每次访问getInstance()方法时都要进行同步,这其实是很浪费的。我们想要的,仅仅是第一次实例化的时候保证同步就可以了,后面的访问getInstance()方法的时候没必要再加锁了。双重检查方式就是为了解决这个问题的。

双重检查

知道了原因,你不假思索就写出了如下代码:

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

private static Singleton instance = null;

private int field1, field2 ...
public static Singleton getInstance() {
// This is incorrect: don't do it at home, kids!
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
private Singleton() {
}
}

但是这样真的是没有任何问题吗?然而并不是,原因是instance = new Singleton();这句话并不是原子操作,这个实例化过程其实可以分成三步:1、 开辟一块内存;2、生成Singleton实例并调用Singleton构造方法;3、将生成的实例地址指向instance。JVM存在指令重排优化,不能保证1–>2—>3的执行顺序,调用链可能是1–>3—>2,3完成的时候,有些field并没有完成初始化,此时若有其他线程进入,因为instance,所以就有可能访问到非法的field,如下图所示:

那如何解决这个问题呢?答案是加volatile关键字

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

private static volatile Singleton instance = null;

private int field1, field2 ...
public static Singleton getInstance() {
// This is incorrect: don't do it at home, kids!
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}

}

为什么这样是可行的呢?其实是Java 5之后对volatile关键字做了修改,它能够禁止编译器进行指令重排,简单地说就是加了volatile关键字之后,编译器能够保证1–>2–>3的执行顺序,因此也就没有上述问题了。更多关于volatile关键字,点击:http://www.cnblogs.com/dolphin0520/p/3920373.html

静态内部类

懒汉式和双重检查方式都要判断实例是否为空,显得不够优雅,下面使用静态内部类的方式则不需要手动写代码判断,线程安由JVM来保证

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {     
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
private Singleton() {
}

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

这种方式可以说是兼具了懒汉式和恶汉式的优点,避免了懒汉式和恶汉式的缺点。

枚举

1
2
3
4
5
6
7
8
9
10
public enum Singleton {     
INSTANCE("field2_vaules")
private String field2;
Singleton(String f) {
field2 = f;
}
public static final Singleton getInstance() {
return INSTANCE;
}
}

枚举的方式是Java大牛Josh Bloch (《Effective Java》作者)推荐使用的,由JVM来保证线程安全,并且能防止反序列化重新生成对象。实在是很妙!

比较

有人对这几种单例进行了测试,并得出了如下结果(出处:http://blog.csdn.net/qq_22706515/article/details/74202814):

当然,这结果在不同机子可能不同,但是可以作为一个参考。

测试代码如下:

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
public class APP {
public static void main(String[] args) {
int threadCount = 100;
long start = System.currentTimeMillis();
final CountLock lock = new CountLock(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10000000; j++) {
//通过更换此处,来测试不同单例实现方式在多线程环境下的性能
SingleDemo5 demo = SingleDemo5.INSTANCE;
}
lock.finish();
}
}).start();
}
//等待所有线程执行完
lock.waitForWrok();
long end = System.currentTimeMillis();
System.out.println("总共耗时" + (end - start));
}
}
public class CountLock {
//线程的总数量
private int count;
public CountLock(int count) {
this.count = count;
}
/**
* 当一个线程完成任务以后,调用一次这个方法
*/
public synchronized void finish() {
count--;
if (count == 0) {
notifyAll();
}
}
/**
* 需要等待其他线程执行完的线程,调用此方法。
*/
public synchronized void waitForWrok() {
while (count > 0) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

总结

推荐使用静态内部类和枚举来实现单例。

参考

Double-checked Locking (DCL) and how to fix it

单例的五种实现方式及其性能分析