When I was reading Wikipedias's article about Double Checked Locking idiom, I'm confused about its implementation:
public class FinalWrapper<T> {
public final T value;
public FinalWrapper(T value) {
this.value = value;
}
}
public class Foo {
private FinalWrapper<Helper> helperWrapper = null;
public Helper getHelper() {
FinalWrapper<Helper> wrapper = helperWrapper;
if (wrapper == null) {
synchronized(this) {
if (helperWrapper == null) {
helperWrapper = new FinalWrapper<Helper>(new Helper());
}
wrapper = helperWrapper;
}
}
return wrapper.value;
}
}
I simply don't understand why we need to create wrapper. Isn't this enough ?
if (helperWrapper == null) {
synchronized(this) {
if (helperWrapper == null) {
helperWrapper = new FinalWrapper<Helper>(new Helper());
}
}
}
Is it because using wrapper can speed up initialization because wrapper is stored on stack and helperWrapper is stored in heap?
In multi-threaded environments, initialization is usually not thread safe, so locking is required to protect the critical section. Since only the first access requires locking, double-checked locking is used to avoid locking overhead of subsequent accesses.
Even though the double-checked locking can potentially speed things up, it has at least two issues: since it requires the volatile keyword to work properly, it's not compatible with Java 1.4 and lower versions. it's quite verbose and it makes the code difficult to read.
No matter how you rig it, double-checked locking still fails Unfortunately, DCL isn't guaranteed to work under the current Java Memory Model (JMM.)
Simply using helperWrapper for both null checks and the return statement could fail due to read reordering allowed under the Java Memory Model.
Here is the example scenario:
helperWrapper == null
(racy read) test evaluates to false, i.e. helperWrapper is not null.return helperWrapper.value
(racy read) results in a NullPointerException, i.e. helperWrapper is nullHow did that happen? The Java Memory Model allows those two racy reads to be reordered, because there was no barrier prior to the read i.e. no "happens-before" relationship. (See String.hashCode example)
Notice that before you can read helperWrapper.value
, you must implicitly read the helperWrapper
reference itself. So the guarantees provided by final
semantics that helperWrapper is fully instantiated do not apply, because they only apply when helperWrapper is not null.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With