面试题答案
一键面试在双重检查锁定单例模式中,将单例实例声明加上volatile
关键字主要有以下原因:
- 防止指令重排序:在Java内存模型中,为了提高性能,编译器和处理器会对指令进行重排序。在单例模式的初始化过程中,可能会出现以下指令重排序问题。假设单例类为
Singleton
,代码如下:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
instance = new Singleton();
这行代码并非是一个原子操作,它实际上分为三步:
- 1. 分配内存空间给Singleton
对象。
- 2. 初始化Singleton
对象。
- 3. 将instance
指向分配的内存空间。
由于指令重排序,步骤2和步骤3可能会交换顺序。在多线程环境下,当线程A执行到instance = new Singleton();
,如果发生了指令重排序,先执行了步骤3,此时instance
已经不为null
,但对象还未初始化完成。如果此时线程B进入getInstance
方法,判断instance != null
,就会直接返回一个未初始化完成的instance
,导致程序出错。
而加上volatile
关键字后,volatile
关键字禁止了指令重排序优化,保证了步骤1、2、3的执行顺序,从而确保在多线程环境下单例对象的正确初始化。
- 保证可见性:
volatile
关键字还能保证变量的可见性。当一个线程修改了volatile
修饰的变量时,会立即将修改后的值刷新到主内存中,其他线程读取该变量时,会从主内存中获取最新的值。在单例模式中,若没有volatile
修饰instance
,当一个线程创建了单例对象并修改了instance
的值后,其他线程可能无法及时看到这个修改,仍然认为instance
为null
,从而再次创建单例对象,破坏了单例模式的唯一性。
如果不加volatile
关键字,在多线程环境下,可能会出现以下对单例模式正确性的影响:
- 创建多个实例:由于指令重排序导致其他线程获取到未初始化完成的对象,从而再次创建实例,破坏单例模式的唯一性。
- 使用未初始化完成的对象:线程获取到一个未完全初始化的单例对象,在使用该对象时可能会引发
NullPointerException
或其他未定义行为,导致程序运行错误。