过了一段时间(是的,我知道花了 2 年时间),我想我有了正确的答案。从字面上看,答案是:
但是,在我看来,这只是这里的问题。只是这里有问题吗?
会是的。按照你现在的方式,getInstance 的调用者永远不会看到null。但如果Singleton 有字段,则无法保证这些字段会正确初始化。
让我们慢慢来,因为这个例子很漂亮,恕我直言。您显示的代码执行单个(活泼)volatile read:
public class Factory {
private Singleton instance;
public Singleton getInstance() {
Singleton res = instance; // <-- volatile RACY read
if (res == null) {
synchronized (this) {
res = instance; // <-- volatile read under a lock, thus NOT racy
if (res == null) {
res = new Singleton();
instance = res;
}
}
}
return res;
}
}
通常,经典的“双重检查锁定”有 两个 volatile 的活泼读取,例如:
public class SafeDCLFactory {
private volatile Singleton instance;
public Singleton get() {
if (instance == null) { // <-- RACY read 1
synchronized(this) {
if (instance == null) { // <-- non-racy read
instance = new Singleton();
}
}
}
return instance; // <-- RACY read 2
}
}
因为这两个读取是活泼的,没有 volatile,所以这种模式被打破了。您可以阅读我们如何破解here, for example。
在您的情况下,有一种优化,可以减少对 volatile 字段的读取。在某些平台上,这很重要,afaik。
问题的另一部分更有趣。如果Singleton 有一些我们需要设置的字段怎么办?
static class Singleton {
//setter and getter also
private Object obj;
}
还有一个工厂,Singleton 是 volatile:
static class Factory {
private volatile Singleton instance;
public Singleton get(Object obj) {
if (instance == null) {
synchronized (this) {
if (instance == null) {
instance = new Singleton();
instance.setObj(obj);
}
}
}
return instance;
}
}
我们有一个不稳定的领域,我们是安全的,对吧?错误的。 obj 的赋值发生在在易失性写入之后,因此不能保证它。简单的英语:this should help you a lot.
解决此问题的正确方法是使用已构建的实例(完全构建)进行 volatile 写入:
if (instance == null) {
Singleton local = new Singleton();
local.setObj(obj);
instance = local;
}