为什么双重检查会带来空指针异常 问题?
java
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
instance = new Singleton();
上述加粗的代码并不是原子操作,包含三个步骤:
-
为 Singleton 分配内存地址
-
执行 Singleton() 构造方法,初始化成员变量等
-
将内存地址赋值给 instance 变量
假设线程 A 正在执行:
java
instance = new Singleton();
由于指令 **重排序,**线程 A 可能在构造函数执行之前,就已经将内存地址赋值给 instance,导致 instance 变为非 null。此时线程 B 会认为 instance 已经是合法对象,于是直接使用,结果可能:
-
访问尚未初始化的字段
-
抛出 NullPointerException
-
出现逻辑错误或不可预知的行为
为什么可以通过 静态内部类 实现懒汉式 单例模式 ?
java
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这依赖于 Java 类加载机制的特性:
-
JVM 在加载外部类的过程中不会加载静态内部类,只有内部类的属性 / 方法被调用时才会进行加载(懒汉式)
-
JVM 在加载类的时候会自动保证线程安全,一个类在 JVM 中只会被加载一次