I do not understand why local variable is needed here:
public class FinalWrapper<T> {
public final T value;
public FinalWrapper(T value) {
this.value = value;
}
}
public class Foo {
private FinalWrapper<Helper> helperWrapper;
public Helper getHelper() {
FinalWrapper<Helper> tempWrapper = helperWrapper;
if (tempWrapper == null) {
synchronized(this) {
if (helperWrapper == null) {
helperWrapper = new FinalWrapper<Helper>(new Helper());
}
tempWrapper = helperWrapper;
}
}
return tempWrapper.value;
}
}
I get this code from: https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java. What issues can we have if we do not have this local variable? According to the wiki article:
Semantics of final field in Java 5 can be employed to safely publish the helper object without using volatile. The local variable tempWrapper is required for correctness: simply using helperWrapper for both null checks and the return statement could fail due to read reordering allowed under the Java Memory Model. Performance of this implementation is not necessarily better than the volatile implementation.
Thanks in advance.
To understand the underlying issue let's remove the local variable from the code:
public class Foo {
private FinalWrapper<Helper> helperWrapper;
public Helper getHelper() {
if (helperWrapper == null) {
synchronized(this) {
if (helperWrapper == null) {
helperWrapper = new FinalWrapper<Helper>(new Helper());
}
}
}
return helperWrapper.value;
}
}
We have three reads in this case:
- The outer null check.
- The inner null check.
- The read before the return.
The problem is that due to the read reordering the first read can return a non-null value and the third read can return null. It means that the third read happens before the first one, which is supposed to ensure helperWrapper
is initialized...
Adding the local variable solves the issue because we assign helperWrapper
value to tempWrapper
and then it does not matter in what order tempWrapper
is read. If it has a non-null value it is used both for the null check and for the return statement.
It can happen because Java Memory Model allows for such reordering of operations just for the optimization purpose. Look at the quote from here:
What is meant by reordering?
There are a number of cases in which accesses to program variables
(object instance fields, class static fields, and array elements) may
appear to execute in a different order than was specified by the
program. The compiler is free to take liberties with the ordering of
instructions in the name of optimization. Processors may execute
instructions out of order under certain circumstances. Data may be
moved between registers, processor caches, and main memory in
different order than specified by the program.
[...]
The compiler, runtime, and hardware are supposed to conspire to create
the illusion of as-if-serial semantics, which means that in a
single-threaded program, the program should not be able to observe the
effects of reorderings. However, reorderings can come into play in
incorrectly synchronized multithreaded programs, where one thread is
able to observe the effects of other threads, and may be able to
detect that variable accesses become visible to other threads in a
different order than executed or specified in the program.
[...]