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?
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;
}
getInIfOpen()
in == null
Et voit que in
n'est pas null
.null
à in
.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.
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.
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).
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.