web-dev-qa-db-fra.com

Pourquoi BufferedInputStream copie-t-il un champ dans une variable locale plutôt que d'utiliser le champ directement

Lorsque je lis le code source de Java.io.BufferedInputStream.getInIfOpen(), je ne comprends pas pourquoi il a écrit du code comme ceci:

/**
 * Check to make sure that underlying input stream has not been
 * nulled out due to close; if not return it;
 */
private InputStream getInIfOpen() throws IOException {
    InputStream input = in;
    if (input == null)
        throw new IOException("Stream closed");
    return input;
}

Pourquoi utilise-t-il l'alias au lieu d'utiliser la variable de champ in directement comme ci-dessous:

/**
 * Check to make sure that underlying input stream has not been
 * nulled out due to close; if not return it;
 */
private InputStream getInIfOpen() throws IOException {
    if (in == null)
        throw new IOException("Stream closed");
    return in;
}

Quelqu'un peut-il donner une explication raisonnable?

107
Saint

Si vous regardez ce code hors contexte, il n'y a pas de bonne explication pour cet "alias". Il s'agit simplement d'un code redondant ou d'un style de code médiocre.

Mais le contexte est que BufferedInputStream est une classe qui peut être sous-classée, et qu'elle doit fonctionner dans un contexte multi-thread.

L'indice est que in est déclaré dans FilterInputStream est protected volatile. Cela signifie qu'il existe une chance qu'une sous-classe puisse atteindre et affecter null à in. Compte tenu de cette possibilité, l'alias est en fait là pour empêcher une condition de concurrence.

Considérez le code sans "l'alias"

private InputStream getInIfOpen() throws IOException {
    if (in == null)
        throw new IOException("Stream closed");
    return in;
}
  1. Le thread A appelle getInIfOpen()
  2. Le thread A évalue in == null Et voit que in n'est pas null.
  3. Le thread B affecte null à in.
  4. Le thread A exécute return in. Ce qui renvoie null car a est un volatile.

L '"alias" empêche cela. Maintenant, in est lu une seule fois par le thread A. Si le thread B affecte null après que le thread A a in, cela n'a pas d'importance. Le thread A lèvera une exception ou renverra une valeur (garantie) non nulle.

119
Stephen C

En effet, la classe BufferedInputStream est conçue pour une utilisation multithread.

Ici, vous voyez la déclaration de in, qui est placée dans la classe parente FilterInputStream:

protected volatile InputStream in;

Comme il s'agit de protected, sa valeur peut être modifiée par n'importe quelle sous-classe de FilterInputStream, y compris BufferedInputStream et ses sous-classes. En outre, il est déclaré volatile, ce qui signifie que si un thread modifie la valeur de la variable, ce changement sera immédiatement reflété dans tous les autres threads. Cette combinaison est mauvaise, car elle signifie que la classe BufferedInputStream n'a aucun moyen de contrôler ou de savoir quand in est modifiée. Ainsi, la valeur peut même être modifiée entre la vérification de null et l'instruction return dans BufferedInputStream::getInIfOpen, ce qui rend la vérification de null inutile. En lisant la valeur de in une seule fois pour la mettre en cache dans la variable locale input, la méthode BufferedInputStream::getInIfOpen est protégé contre les modifications d'autres threads, car les variables locales appartiennent toujours à un seul thread.

Il y a un exemple dans BufferedInputStream::close, qui définit in sur null:

public void close() throws IOException {
    byte[] buffer;
    while ( (buffer = buf) != null) {
        if (bufUpdater.compareAndSet(this, buffer, null)) {
            InputStream input = in;
            in = null;
            if (input != null)
                input.close();
            return;
        }
        // Else retry in case a new buf was CASed in fill()
    }
}

Si BufferedInputStream::close est appelé par un autre thread tandis que BufferedInputStream::getInIfOpen est exécuté, cela entraînerait la condition de concurrence décrite ci-dessus.

20
Stefan Dollase

C'est un code si court, mais, théoriquement, dans un environnement multi-thread, in peut changer juste après la comparaison, donc la méthode pourrait retourner quelque chose qu'elle n'a pas vérifié (elle pourrait retourner null, faisant ainsi exactement ce qu'il était censé empêcher).

6
acdcjunior

Je crois que la capture de la variable de classe in dans la variable locale input est pour éviter un comportement incohérent si in est modifié par un autre thread pendant que getInIfOpen() est en cours d'exécution.

Notez que le propriétaire de in est la classe parente et ne la marque pas comme final.

Ce modèle est reproduit dans d'autres parties de la classe et semble être un codage défensif raisonnable.

4
Sam