双重检查锁定(DCL)模式中的问题
在单例模式中,双重检查锁定(Double-Checked Locking,DCL)是一个常用的优化方法,旨在避免每次获取实例时都进入同步代码块,提高性能。双重检查锁定的代码通常如下:
public class Singleton {
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
问题:在双重检查锁定中,instance = new Singleton()
不是一个原子操作。这个操作可以分解成以下几个步骤:
- 分配内存空间。
- 初始化对象。
- 将内存地址赋给
instance
变量。
在多线程环境中,指令可能会发生重排序,因此可能先执行步骤 3,然后再执行步骤 2。这就会导致一个问题:其他线程在第一次检查时发现 instance
不为 null
,但实际上对象还未初始化完成,从而可能出现空指针异常。
volatile
关键字的作用
volatile
关键字可以解决上述问题。它的作用主要有以下两点:
-
可见性:当一个线程修改了
volatile
变量的值,其他线程会立即得知这个变化。这保证了多线程之间的变量值的一致性。 -
有序性:
volatile
关键字会禁止指令重排序。这样在instance = new Singleton()
操作中,步骤 2(对象初始化)一定会发生在步骤 3(赋值给变量instance
)之前,从而避免了未初始化对象的引用。
因此,通过将instance
声明为volatile
,可以保证双重检查锁定中的对象创建过程的有序性,避免空指针异常的发生:
private static volatile Singleton instance;
总结
- 为什么用
volatile
:volatile
保证了指令的可见性和有序性,避免了重排序导致的空指针问题。 - 效果:在双重检查锁定模式下,通过使用
volatile
可以保证实例的正确创建,并避免多线程环境下的空指针异常。