[JAVA] Punkt 83: Verwenden Sie die verzögerte Initialisierung mit Bedacht
83. Eine verzögerte Initialisierung sollte mit Vorsicht angewendet werden
- Verzögerte Initialisierung bezieht sich auf den Vorgang der Initialisierung nur, wenn ein Wert benötigt wird. Eine verzögerte Initialisierung wird hauptsächlich durchgeführt, um die Verarbeitungsleistung zu verbessern, kann jedoch auch schädlich sein.
- Eine verzögerte Initialisierung spart die Kosten für die Klasseninitialisierung und -instanziierung, erhöht jedoch die Kosten für den Zugriff auf Felder, die eine verzögerte Initialisierung durchführen.
- Wenn das zu verzögernde zu initialisierende Feld nur ein Teil der Instanz der Klasse ist und die Initialisierungskosten des Feldes hoch sind, kann die verzögerte Initialisierung wirksam sein. Die einzige Möglichkeit, den Effekt zu überprüfen, besteht darin, den Leistungsunterschied ohne verzögerte Initialisierung mit dem Fall zu vergleichen.
- Eine verzögerte Initialisierung in einer Multithread-Umgebung kann schwierig sein. Die in diesem Kapitel beschriebenen Initialisierungstechniken sind threadsicher.
- In den meisten Situationen ist eine normale Initialisierung einer verzögerten Initialisierung vorzuziehen. Das Folgende ist ein normales Initialisierungsbeispiel.
// Normal initialization of an instance field
private final FieldType field = computeFieldValue();
- Bei der Verwendung der verzögerten Initialisierung sollten synchronisierte Accessoren verwendet werden, um den Initialisierungszyklus zu verhindern, da dies einfach und klar ist.
// Lazy initialization of instance field - synchronized accessor
private FieldType field;
private synchronized FieldType getField() {
if (field == null)
field = computeFieldValue();
return field;
}
- Die beiden oben genannten Notationen ändern sich auch dann nicht, wenn das Feld statisch ist (es sei denn, sowohl das Feld als auch die Zugriffsmethode sind statisch).
- Wenn für statische Felder eine verzögerte Initialisierung verwendet wird, um die Leistung zu verbessern, kann dies wie folgt geschrieben werden (Klasse-Idiom für Inhaber einer verzögerten Initialisierung).
// Lazy initialization holder class idiom for static fields
private static class FieldHolder {
static final FieldType field = computeFieldValue();
}
private static FieldType getField() { return FieldHolder.field; }
- Wenn Sie eine verzögerte Initialisierung für beispielsweise Felder durchführen möchten, um die Leistung zu verbessern, können Sie diese wie folgt schreiben (überprüfen Sie die Redewendung).
Auf diese Weise wird es, wenn es initialisiert wurde, nicht gesperrt.
Um zu erklären, wie es geschrieben wird, gibt es hier zwei Prüflogiken für Feldvariablen. Überprüfen Sie das erste Mal ohne Sperren. Wenn das Ergebnis der Überprüfung nicht initialisiert ist, sperren Sie und überprüfen Sie es erneut. Wenn die Initialisierung auch bei der zweiten Prüfung noch nicht abgeschlossen ist, führen Sie die Initialisierungsmethode aus.
Nach der Initialisierung muss das Feld als flüchtig deklariert werden, da es sich nicht mehr um einen Sperrvorgang handelt.
// 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;
}
- Es sieht so aus, als ob Sie das Ergebnis im obigen Code nicht benötigen, aber es scheint ungefähr 1,4-mal schneller zu sein als ohne es (** Ich weiß nicht, warum es schneller ist **).
- Das Double-Check-Idiom kann auch auf statische Felder angewendet werden, aber das Idiom der Klasse für die verzögerte Initialisierung ist besser.
- Entfernen Sie bei Double-Check-Redewendungen die zweite Prüfung, um mehreren Initialisierungen standhalten zu können (Single-Check-Idiom).
(** Ich weiß nicht, warum Single-Check-Idiom geeignet ist, um mehreren Initialisierungen standzuhalten ... **)
// 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;
}