懒汉式单例中双重检查的好处

记得前阵子面试金山时碰到了这样一个问题

1)下面哪一种写法更好,为什么?
2)请写出另一种单例模式。

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

private static Singleton singleton;

private Singleton() {}

public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
singleton = new Singleton();
}
}
return singleton;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Singleton {

private static volatile Singleton singleton;

private Singleton() {}

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

很显然,第一种代码块是线程不安全的。
比如说这种情况,有一个线程调用了getInstance(),但执行到if(singleton==null)这一步时,另一个线程也进入了这个方法,那么便会造成最终创建了两个Singleton实例。
而若是第二种代码块,在同步锁中加入第二次验证,就不怕多个线程同时进入方法体导致的多个实例被创建了。

以上两个方法块俗称懒汉式单例模式,意为调用时再创建实例对象。

另一种单例模式称为饿汉式单例模式,不同的点在于类装载时便完成了类实例化,以下两种方法块都是饿汉式的表现方法,是一样的。

1
2
3
4
5
private static Singleton instance = new Singleton();

static {
instance = new Singleton();
}

最后再推荐个与双重检查一样线程安全的,静态内部类实现的懒汉式单例模式。

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

这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。

类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。