我在Wikipedia上针对Singleton模式遇到了这段代码。任何人都可以解释两次检查null的目的/逻辑吗?
public class SingletonDemo {
private static volatile SingletonDemo instance = null;
private SingletonDemo() { }
public static SingletonDemo getInstance() {
if (instance == null) {
synchronized (SingletonDemo .class){
if (instance == null) {
instance = new SingletonDemo ();
}
}
}
return instance;
}
}
答案 0 :(得分:2)
答案 1 :(得分:1)
最好使用内部类进行延迟初始化而不是“双重检查锁定”:
public class Singleton {
// Private constructor prevents instantiation from other classes
private Singleton() {}
/**
* SingletonHolder is loaded on the first execution of Singleton.getInstance()
* or the first access to SingletonHolder.INSTANCE, not before.
*/
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
双重检查锁定并非万无一失。 JVM保证静态内部类可以懒惰地创建线程安全的Singleton类。
答案 2 :(得分:0)
假设有两个线程T1和T2调用相同的方法。
现在T1通过第一次检查,发现实例为空,然后进入睡眠状态。让我们说T2开始(可能不会一直发生,但肯定会在某些时候发生),然后通过第一次检查并发现该对象为空,获取锁并创建对象。
现在T1醒来并获得锁定。此时,如果我们不再检查null,T1将永远不会知道T2已经创建了一个对象。因此,仔细检查。
您现在可能会问,为什么不在开头进行同步和空检查?如果我们一直同步检查null,那么这个方法一次只能访问单个线程,并导致性能问题,代价是什么?只是为了在程序开始时有一些效率,当只有2或3个线程试图创建该实例时。
答案 3 :(得分:0)
当getInstance
仍为instance
时,多个线程可能会调用null
。
只有一个线程进入同步块(比方说T1),其他线程将等待。
当T1退出同步块时,其他一些线程将进入。
然后,如果没有if (instance == null)
,则会创建Singleton
的新实例。
答案 4 :(得分:0)
此代码假定并发(同时使用该类的多个线程)。
代码首先尝试查看是否有实例。如果没有,则尝试创建一个。为避免其他线程在执行此操作的同时执行此操作,它会使用synchronized
阻止代码。
现在,在第一个if (instance == null)
和synchronized
之间,另一个更快的线程可能已经挤进并创建了实例,因此代码再次检查以确保。