设计模式 - 单例模式 - Tips
为什么双重检查会带来空指针异常问题?
if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } }
}
instance = new Singleton();
上述加粗的代码并不是原子操作,包含三个步骤:
-
为 Singleton 分配内存地址
-
执行 Singleton() 构造方法,初始化成员变量等
-
将内存地址赋值给 instance 变量
假设线程 A 正在执行:
instance = new Singleton();
由于指令重排序,线程 A 可能在构造函数执行之前,就已经将内存地址赋值给 instance,导致 instance 变为非 null。此时线程 B 会认为 instance 已经是合法对象,于是直接使用,结果可能:
-
访问尚未初始化的字段
-
抛出 NullPointerException
-
出现逻辑错误或不可预知的行为
为什么可以通过静态内部类实现懒汉式单例模式?
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 中只会被加载一次