Double checked locking 双重检查锁

实现单例模式的方法有好几种,例如饿汉式、懒汉式、双重检查。其中双重检查才适用于多线程的情况,其他代码都会造成线程不安全,例如:

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton{
private static Singleton singleton;
private Singleton(){

}
public Singleton getInstance(){
if(null == singleton){
singleton = new Singleton();
}
return singleton;
}
}

若有两个线程A和B同时访问上述代码,线程A检查到singleton为空,在还没有创建实例前,线程B进来了,它也检查到singleton为空,那么最后会生成两个实例对象,这违背了单例模式的定义。

那么,为了解决上述问题,我们可以对getInstance()方法进行加锁。但是用synchronized直接对方法加锁会导致很大的性能开销。更好的方法是使用双重检查锁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//错误的方法
public class Singleton{
private static Singleton singleton;
private Singleton(){

}
public Singleton getInstance(){
if(null == singleton){
synchronized (Singleton.class){
if(null == singleton){
singleton = new Singleton();
}
}
}
return singleton;
}
}

这样就算有多个线程都通过了第一次检查,只会有一个线程可以去执行初始化,其他线程在进入第二次判断是否为空的时候得到的结果只会是不为空,所以直接返回,解决了性能消耗问题。

但是要知道,指令是可以重排序的,为了避免singleton = new Singleton()这句指令的重排带来的问题,正确的双重检查应该在共享变量singleton前面加上volatile修饰,禁止重排序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//正确的写法
public class Singleton{
private volatile static Singleton singleton;
private Singleton(){

}
public Singleton getInstance(){
if(null == singleton){
synchronized (Singleton.class){
if(null == singleton){
singleton = new Singleton();
}
}
}
return singleton;
}
}