To lazily initialize a variable of a reference type in a multithreaded environment, we either use the Initialization-On-Demand Holder or use the Double-Checked Locking with the variable being qualified by the volatile
keyword. The variable does not need to be qualified by volatile
if it is a 32-bit primitive such as int
and float
, because there are no "uninitialized non-null" primitives. See The "Double-Checked Locking is Broken" Declaration.
For example, the following code snippet implements a lazily initialized hashCode()
:
-
public class Foobar {
-
-
private int hashCode = 0; // no need to be volatile
-
-
@Override
-
public int hashCode() {
-
int value = hashCode;
-
if(0 == value) {
-
synchronized(this) {
-
value = hashCode;
-
if(0 == value) {
-
value = initialize(); // return a non-zero value
-
hashCode = value;
-
}
-
}
-
}
-
return value;
-
}
-
}
The caveats of using this technique are: 1) The uninitialized value and the initialized value need to be different, so that when a thread sees the unintialized value, it knows that it needs to synchronize. 2) This technique only guarantees the memory consistency of the primitive itself to which the technique is applied. The memory consistency of any other shared variable needs to be proved separately to be valid. See the bottom of http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/package-summary.html for the reference of memory consistency properties of commonly used language features.
Post new comment