考虑下面的代码
public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) { //1
if (instance == null) //2
instance = new Singleton(); //3
}
}
return instance;
}
双重检查锁定背后的理论是 //2 处的第二次检查使得不可能像清单中那样创建两个不同的 Singleton 对象
考虑以下事件序列:
线程 1 进入 getInstance() 方法。
线程 1 在 //1 处进入同步块,因为实例为空。
线程 1 被线程 2 抢占。
线程 2 进入 getInstance() 方法。
线程 2 尝试在 //1 处获取锁,因为实例仍然为空。但是,因为线程 1 持有锁,所以线程 2 在 //1 处阻塞。
线程 2 被线程 1 抢占。
线程 1 执行并且因为实例在 //2 处仍然为空,所以创建了一个单例对象并将其引用分配给实例。
线程 1 退出同步块并从 getInstance() 方法返回实例。
线程 1 被线程 2 抢占。
线程 2 在 //1 处获取锁并检查实例是否为空。
因为 instance 不为空,所以不会创建第二个 Singleton 对象,而是返回由线程 1 创建的对象。
双重检查锁定背后的理论是完美的。不幸的是,现实完全不同。双重检查锁定的问题在于不能保证它可以在单处理器或多处理器机器上工作。双重检查锁定失败的问题不是由于 JVM 中的实现错误,而是由于当前的 Java 平台内存模型。内存模型允许所谓的“乱序写入”,这也是这个习惯用法失败的主要原因。