Before dismissing this, it is possible to implement double checked locking without volatile, see below. I'm suggesting a variation on this, that gets rid of the local variable.
The following is a correct implementation of double checked locking from 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;
}
}
}
I wonder if it would be possible to get rid of the local variable 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?
}
}
}
In 17.4.8. Executions and Causality Requirements of the JLS 8 it is written:
Informally, we allow an action to be committed early if we know that the action can occur without assuming some data race occurs.
The big question here is if the last read (read 5) can be reordered so that we could potentially see a non-null wrapper in read 1 and still see a null in the last read. This should not be allowed to happen in the first invocation of get()
by a thread because then the only way for the last read to occur would be because of a data race and the JMM would prohibit the reordering.
In subsequent invocations of get()
reordering would be allowed but then it shouldn't matter because wrapper should be visible anyways.