题目

设计一个类,只能生成这个类的一个实例

单例模式是 GOF 23 种设计模式当中的一种,实现起来较为简单,只需要将构造方法设置为私有 priavte,并提供一个静态方法获取该类实例即可。代码如下:

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

private static Singleton singleton = null;

// 私有构造方法
private Singleton(){

}

// 懒汉式单例模式
public static Singleton getInstance(){
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}

想要获取 Singleton 这个类的实例的话只需如下代码即可

1
Singleton singleton = Singleton.getInstance();

但是这种写法有一个问题,多线程情况下调用 getInstance() 方法时可能会被其他线程抢占执行权,最终生成多个实例。

因此,在多线程情况下,为保证单实例,需要加锁。代码如下:

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

private static Singleton singleton = null;

// 私有构造方法
private Singleton(){

}

// 懒汉式线程安全单例模式
public static Singleton getInstance(){
synchronized(Singleton.class){
if (singleton == null) {
singleton = new Singleton();
}
}

return singleton;
}
}

这样写已经没什么大问题了,每一个线程获取 Singleton 实例的时候都需要去拿锁,拿到锁之后生成实例,最终生成的实例只有一个。

但是如果考虑到性能的因素,那么还有更好的写法。

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

private static Singleton singleton = null;

// 私有构造方法
private Singleton(){

}

// 懒汉式双锁单例模式
public static Singleton getInstance(){
if (singleton == null) {
synchronized(Singleton.class){
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}

加锁会影响方法调用的效率,如果在拿锁之前进行判定是否实例化的话可以节省很多时间,这种写法能在保证多线程安全的前提下保持性能。

当然上面这种双锁的方式较为复杂,如果不在乎内存的话,可以采用饿汉式,类加载就初始化实例,这样的话也能保证线程安全和高性能。

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

private static Singleton singleton = new Singleton();

// 私有构造方法
private Singleton(){

}

// 饿汉式
public static Singleton getInstance(){
return singleton;
}
}

Effctive Java的作者则提倡使用enum来实现单例模式,保证线程安全并且还支持序列化,不过Java1.5之后才加入enum机制,在兼容性上可能有些问题。

我还没来得及拜读Effctive Java,因此这种写法不是很了解,有兴趣可以自己去看看。