在解除此之前,可以实现双重检查锁定而不用 volatile,见下文。我建议对此进行修改,摆脱局部变量。
以下是来自Shipilev的双重检查锁定的正确实现:
public class FinalWrapperFactory {
private FinalWrapper wrapper;
public Singleton get() {
FinalWrapper w = wrapper;
if (w == null) { // check 1
synchronized(this) {
w = wrapper;
if (w == null) { // check2
w = new FinalWrapper(new Singleton());
wrapper = w;
}
}
}
return w.instance;
}
private static class FinalWrapper {
public final Singleton instance;
public FinalWrapper(Singleton instance) {
this.instance = instance;
}
}
}
我想知道是否有可能摆脱局部变量w
:
public class FinalWrapperFactory {
private FinalWrapper wrapper; //same as example above
public Singleton get() {
if (wrapper == null) { // read 1
synchronized(this) {
if (wrapper == null) { // read 2
wrapper = new FinalWrapper(new Singleton());
return wrapper.instance; // read 3
} else {
return wrapper.instance; // read 4
}
}
} else {
return wrapper.instance; // read 5 (last read). Can this be reordered?
}
}
}
在17.4.8. Executions and Causality Requirements的JLS 8中写道:
非正式地,如果我们知道,我们允许提前采取行动 可以在不假设发生某些数据竞争的情况下发生此操作。
这里的一个重要问题是,如果最后一次读取(读取5)可以重新排序,那么我们可能会在读取1中看到非空的包装器,并且在上次读取时仍然看到空值。不应该在线程第一次调用get()
时发生这种情况,因为最后一次读取的唯一方法是因为数据竞争而JMM会禁止重新排序。
在随后的get()
调用中,将允许重新排序,但它应该无关紧要,因为无论如何都应该可以看到包装器。