一、双重检查锁
先看看常见错误写法:
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
错误解析:
由于指令重排优化,可能会导致初始化单例对象和将该对象地址赋值给 instance 字段的顺序与上面 Java 代码中书写的顺序不同。例如,线程 A 在创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时线程 A 就可以将分配的内存地址赋值给 instance 字段了,然而该对象可能还没有初始化。线程B来调用 getInstance()
方法,得到的就是未初始化完全的单例对象,这就会导致系统出现异常行为。
备注:上面所说的初始化单例对象和将该对象地址赋值给 instance 字段是指:
instance = new Singleton()
。这段代码涉及到两个指令,初始化单例对象:new Singleton()
;将该对象地址赋值给 instance 字段:instance = 内存地址
。在构造方法被调用之前就是指new Singleton()
。
为了解决该问题,我们可以使用 volatile
关键字修饰 instance
字段。volatile
关键字的一个语义就是禁止指令的重排序优化,从而保证 instance
字段被初始化时,单例对象已经完全被完全初始化。最终得到的代码如下所示:
public class Singleton {
/**
* 使用 volatile 关键字修饰,避免指令重排序
*/
private volatile static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
二、静态内部类
public class Singleton {
private Singleton() {}
private static class SingletonHolder{
private static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
熟悉 Java 类加载机制的都知道,当第一次访问类中的静态字段时,会触发类加载,并且同一个类只加载一次。静态内部类也是如此,类加载过程由类加载器负责加锁,从而保证线程安全。此种单例模式更加简洁明了,不容易出错。