代码实现

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

开始问为什么

为什么 instance 需要 static 修饰

单例的访问一般是通过类名进行,在该例中,如果要获取实例对象方法是 DoubleCheck.getInstance() ,即通过静态方法去获取,静态方法只能访问静态变量,因此 instance 必须是静态变量。

为什么需要第一个判空

此次判断可以在类初始化完成后不再进入同步代码块去等待获取锁,进而去初始化。提高效率。

为什么需要 synchronized 加锁

此处加锁可以将同步块降低为最小粒度。多线程条件下,同步块越小,效率越高。加锁之后进行初始化,防止其他线程重复初始化。

同步块中为什么需要再次判空

线程之间的切换是由 CPU 决定的。如果线程 A 在 执行完 new DoubleCheck() 后,还未赋值给 instance ,此时发生了线程切换。线程 B 在第一次判断时会发现 instance 是 null ,这样就进入同步代码块等待锁释放。然后再切换到线程 A 执行,赋值给 instance 并释放锁。这时线程 B 获取锁,如果没有第二次判空,则会将 instance 再初始化一次。

为什么 instance 需要 volatile 修饰

关键字 volatile 在这里使用是为了保证变量 instance 的可见性。即 new DoubleCheck() 初始化类完成之后,赋值给 instance ,此时对其他线程在拿到 instance 时肯定是非 null 的。如果不加 volatile 关键字,线程 A 在释放锁之后,instance 变量仅存在了 CPU 缓存中,而没有写入主存,此时线程 B 获取锁并判断 instance 为空,会再次初始化。