-
Notifications
You must be signed in to change notification settings - Fork 2k
Description
CHAPTER 11 CONCURRENCY
Item 83: Use lazy initialization judiciously
// Double-check idiom for lazy initialization of instance fields
private FieldType getField() {
FieldType result = field; // (1)
if (result == null) { // First check (no locking)
synchronized (this) {
if (field == null) // Second check (with locking)
field = result = computeFieldValue(); // (2)
}
}
return result;
}
This code is broken, because when one thread enters the line (2), other threads may have already read the field into result (line (1)), and it is null, so, when they enter the line (2), they see that field isn't null anymore, because it was already initialized by the first thread so, the method called by those non-first threads, when they start almost immediately after the first thread, returns null in spite that the field was initialized.
To fix this we need to reassign result to the field value again.
Here is a solution that works:
private FieldType getField() {
FieldType result = field;
if (result == null) { // First check (no locking)
synchronized (this) {
if (field == null) // Second check (with locking)
field = result = computeFieldValue();
else
result = field;
}
}
return result;
}
Or this one, which I like even more, because we check if result is not null first, which happens more often, or to be more precise, always after first initialization.
private FieldType getField() {
FieldType result = field;
if (result != null)
return result;
synchronized (this) {
result = field;
return result != null ? result : (field = computeFieldValue());
}
}
Attached code demonstrates the error and how the fixes work.
I'm writing this not to accuse (everybody can mistake) Joshua Bloch, whom I respect much and find his book very useful, but to suggest to programmers to use copy-paste technique judiciously.
Thank you.