0

J'utilise la classe LazyReference depuis quelques années (pas régulièrement, bien sûr, mais parfois c'est très utile). La classe peut être vue here. Les crédits vont à Robbie Vanbrabant (auteur de la classe) et Joshua Bloch avec son fameux "Effective Java 2nd edt". (code d'origine).LazyReference avec double-contrôle de verrouillage et manipulation de zéro

La classe fonctionne correctement (dans Java 5+) mais il y a un petit problème potentiel. Si instanceProvider renvoie null (bien il ne doit pas selon le contrat Provider.get() de Guice, mais ...) puis à chaque exécution de la méthode LazyReference.get() le verrouillage sera tenu et instanceProvider.get sera appelé encore et encore. Cela ressemble à une bonne punition pour ceux qui brisent les contrats (he-he), mais que se passe-t-il s'il faut vraiment paresseusement initialiser un champ avec la possibilité de définir la valeur null?

J'ai modifié LazyReference un peu:

public class LazyReference<T> { 

    private final Object LOCK = new Object(); 

    private volatile T instance; 

    private volatile boolean isNull; 

    private final Provider<T> instanceProvider; 

    private LazyReference(Provider<T> instanceProvider) { 
    this.instanceProvider = instanceProvider; 
    } 

    public T get() { 
    T result = instance; 
    if (result == null && !isNull) { 
     synchronized (LOCK) { 
     result = instance; 
     if (result == null && !isNull) { 
      instance = result = instanceProvider.get(); 
      isNull = (result == null); 
     } 
     } 
    } 
    return result; 
    } 
} 

à mon humble avis, il devrait fonctionner très bien (si vous avez un autre avis s'il vous plaît poster vos commentaires et critiques). Mais je me demande ce qui se passera si je supprime le modificateur volatile de isNull booléen (en laissant pour instance bien sûr)? Cela fonctionnera-t-il toujours correctement?

Répondre

2

Comme l'a souligné Neil Coffey, ce code contient une condition de course, mais il peut être facilement fixé comme suit (notez que instance ne pas besoin d'être volatile):

public class LazyReference<T> {  
    private T instance; 
    private volatile boolean initialized; 
    ... 
    public T get() { 
    if (!initialized) { 
     synchronized (LOCK) { 
     if (!initialized) { 
      instance = instanceProvider.get(); 
      initialized = true; 
     } 
     } 
    } 
    return instance; 
    } 
} 
+0

'l'instance n'a pas besoin d'être volatile' - Bon point! Puisque 'instance' est écrit avant le champ volatil' initialized', il sera visible par tous les autres threads qui lisent 'initialized'. – Idolon

3

Le code ci-dessus a une condition de concurrence: instance peut être définie sur la "vraie" null à partir du résultat de instanceProvider.get() avant que isNull ait été défini.

Etes-vous sûr qu'il ne serait pas plus facile pour vous de simplement jeter ce non-sens compliqué et synchroniser correctement. Je parie que vous ne serez pas en mesure de mesurer toute différence de performance et il sera plus facile de vérifier que votre code est correct.

+0

Oui vous avez raison: 'instance' peut être défini sur" real "' null' avant que 'isNull' soit défini sur' true'. Mais dans ce cas, l'exécution passera par un bloc synchronisé, dans lequel la valeur de 'isNull' sera déjà égale à 'true'. Ensuite, la seconde vérification conditionnelle évaluera 'false' et' instanceProvider.get' ne sera pas appelée plus d'une fois. Ou il me manque quelque chose? – Idolon

+0

Répondre à votre question: oui, il serait plus facile d'utiliser la synchronisation normale ou d'utiliser l'original 'LazyReference' (qui fonctionne parfaitement si on respecte le contrat du fournisseur). Le but de la question est un intérêt théorique personnel. ** axtavt ** a déjà posté une solution plus simple et élégante que la mienne - eh bien, je m'entraînerai plus. – Idolon

Questions connexes