[JAVA] Item 83: Use lazy initialization judiciously
83. Delayed initialization should be used with caution
- Delayed initialization refers to the operation of initializing only when a value is needed. Delayed initialization is mainly done to improve processing performance, but it can also be harmful.
- Lazy initialization saves the cost of class initialization and instantiation, but increases the cost of accessing the fields that perform lazy initialization.
- If the field to be deferred initialized is only a part of the instance of the class and the initialization cost of the field is high, the deferred initialization may be effective. The only way to check the effect is to compare the performance difference with the case without delayed initialization.
- Lazy initialization in a multithreaded environment can be tricky. The initialization techniques described in this chapter are thread-safe.
- In most situations, normal initialization is preferable to delayed initialization. The following is a normal initialization example.
// Normal initialization of an instance field
private final FieldType field = computeFieldValue();
- Synchronized accessors should be used to prevent the initialization cycle using delayed initialization because it is simple and clear.
// Lazy initialization of instance field - synchronized accessor
private FieldType field;
private synchronized FieldType getField() {
if (field == null)
field = computeFieldValue();
return field;
}
- The above two ways of writing do not change even if the field is static (unless both the field and accessor method are static).
- If lazy initialization is used for static fields to improve performance, it can be written as follows (lazy initialization holder class idiom).
// Lazy initialization holder class idiom for static fields
private static class FieldHolder {
static final FieldType field = computeFieldValue();
}
private static FieldType getField() { return FieldHolder.field; }
- If you want to perform lazy initialization for instance fields to improve performance, you can write it as follows (double-check idiom).
In this way of writing, if it has been initialized, it will not be locked.
To explain how to write it, here there are two check logics for field variables. The first time, check without locking, and if the result of the check is not initialized, lock and check again. If the initialization has not been completed even in the second check, run the initialization method.
Once initialized, the field must be declared volatile as it is no longer a lock-based process.
// Double-check idiom for lazy initialization of instance fields
private volatile FieldType field;
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();
}
}
return result;
}
- It looks like you don't need the result in the above code, but it seems to be about 1.4 times faster than it would be without it (** I don't know why it's faster **).
- Double-check idiom can also be applied to static fields, but lazy initialization holder class idiom is better.
- For double-check idioms, remove the second check to be able to withstand multiple initializations (single-check idiom).
(** I don't know why single-check idiom is suitable for being able to withstand multiple initializations ... **)
// Single-check idiom - can cause repeated initialization!
private volatile FieldType field;
private FieldType getField() {
FieldType result = field;
if (result == null)
field = result = computeFieldValue();
return result;
}