[JAVA] Point 83: Utilisez judicieusement l'initialisation paresseuse
83. L'initialisation différée doit être utilisée avec prudence
- L'initialisation retardée fait référence à l'opération d'initialisation uniquement lorsqu'une valeur est nécessaire. L'initialisation retardée est principalement effectuée pour améliorer les performances de traitement, mais elle peut également être nuisible.
- L'initialisation paresseuse économise le coût d'initialisation et d'instanciation de classe, mais augmente le coût d'accès aux champs qui effectuent une initialisation paresseuse.
- Si le champ à initialiser différé n'est qu'une partie de l'instance de la classe et que le coût d'initialisation du champ est élevé, l'initialisation différée peut être efficace. La seule façon de vérifier l'effet est de comparer la différence de performance avec le cas sans initialisation retardée.
- Une initialisation paresseuse dans un environnement multithread peut être délicate. Les techniques d'initialisation décrites dans ce chapitre sont thread-safe.
- Dans la plupart des situations, une initialisation normale est préférable à une initialisation différée. Voici un exemple d'initialisation normal.
// Normal initialization of an instance field
private final FieldType field = computeFieldValue();
- Les accesseurs synchronisés doivent être utilisés lors de l'utilisation d'une initialisation retardée pour empêcher les cycles d'initialisation car ils sont simples et clairs.
// Lazy initialization of instance field - synchronized accessor
private FieldType field;
private synchronized FieldType getField() {
if (field == null)
field = computeFieldValue();
return field;
}
- Les deux méthodes d'écriture ci-dessus ne changent pas même si le champ est statique (à moins que le champ et la méthode d'accès ne soient statiques).
- Si l'initialisation paresseuse est utilisée pour les champs statiques pour améliorer les performances, elle peut être écrite comme suit (idiome de classe de support d'initialisation paresseuse).
// Lazy initialization holder class idiom for static fields
private static class FieldHolder {
static final FieldType field = computeFieldValue();
}
private static FieldType getField() { return FieldHolder.field; }
- Si vous souhaitez effectuer une initialisation tardive des champs d'instance pour améliorer les performances, vous pouvez l'écrire comme suit (double-vérification idiome).
Dans cette façon d'écrire, s'il a été initialisé, il ne sera pas verrouillé.
Pour expliquer comment l'écrire, il existe ici deux logiques de contrôle pour les variables de champ. La première fois, vérifiez sans verrouillage, et si le résultat de la vérification n'est pas initialisé, verrouillez et vérifiez à nouveau. Si l'initialisation n'a pas été terminée même lors de la deuxième vérification, exécutez la méthode d'initialisation.
Une fois initialisé, le champ doit être déclaré volatil car il ne s'agit plus d'un processus de verrouillage.
// 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;
}
- Il semble que vous n'avez pas besoin du résultat dans le code ci-dessus, mais il semble être environ 1,4 fois plus rapide sans lui (** Je ne sais pas pourquoi c'est plus rapide **).
- L'idiome de double vérification peut également être appliqué aux champs statiques, mais l'idiome de classe de détenteur d'initialisation paresseuse est meilleur.
- Pour les idiomes à double vérification, supprimez la deuxième vérification (idiome à vérification unique) pour pouvoir supporter plusieurs initialisations.
(** Je ne sais pas pourquoi l'idiome à vérification unique convient pour pouvoir supporter plusieurs initialisations ... **)
// 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;
}