variable una tipos que locales las instancia globales ejemplos definicion datos constante clase ambito java bufferedinputstream

java - tipos - ¿Por qué BufferedInputStream copia un campo a una variable local en lugar de usar el campo directamente?



variables de instancia java (4)

Creo que capturar la variable de clase in la input variable local es evitar un comportamiento incoherente si se cambia por otro hilo mientras se ejecuta getInIfOpen() .

Tenga en cuenta que el propietario de in es la clase padre y no lo marca como final .

Este patrón se replica en otras partes de la clase y parece ser una codificación defensiva razonable.

Cuando leo el código fuente de java.io.BufferedInputStream.getInIfOpen() , estoy confundido acerca de por qué escribió un código como este:

/** * 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; }

¿Por qué está usando el alias en lugar de usar la variable de campo directamente como se muestra a continuación?

/** * 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; }

¿Alguien puede dar una explicación razonable?


Este es un código tan corto, pero, en teoría, en un entorno multiproceso, puede cambiar justo después de la comparación, por lo que el método podría devolver algo que no comprobó (podría devolver null , haciendo exactamente lo que era destinado a prevenir).


Esto se debe a que la clase BufferedInputStream está diseñada para uso de subprocesos múltiples.

Aquí puede ver la declaración de in , que se coloca en la clase primaria FilterInputStream :

protected volatile InputStream in;

Como está protected , su valor puede ser cambiado por cualquier subclase de FilterInputStream , incluyendo BufferedInputStream y sus subclases. Además, se declara volatile , lo que significa que si algún hilo cambia el valor de la variable, este cambio se reflejará inmediatamente en todos los otros hilos. Esta combinación es mala, ya que significa que la clase BufferedInputStream no tiene forma de controlar o saber cuándo se cambia en. Por lo tanto, el valor incluso se puede cambiar entre la comprobación de nulo y la declaración de retorno en BufferedInputStream::getInIfOpen , lo que efectivamente hace que la comprobación de nulo sea inútil. Al leer el valor de in solo una vez para almacenarlo en caché en la input variable local, el método BufferedInputStream::getInIfOpen está a salvo de los cambios de otros subprocesos, ya que las variables locales siempre son propiedad de un solo subproceso.

Hay un ejemplo en BufferedInputStream::close , que se establece in nulo:

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 otro hilo llama a BufferedInputStream::close mientras se ejecuta BufferedInputStream::getInIfOpen , esto daría como resultado la condición de carrera descrita anteriormente.


Si observa este código fuera de contexto, no hay una buena explicación para ese "alias". Es simplemente código redundante o estilo de código pobre.

Pero el contexto es que BufferedInputStream es una clase que puede subclasificarse y que necesita funcionar en un contexto de subprocesos múltiples.

La pista es que se declara en FilterInputStream está protected volatile . Eso significa que existe la posibilidad de que una subclase pueda ingresar y asignar null a in . Dada esa posibilidad, el "alias" está realmente allí para prevenir una condición de carrera.

Considere el código sin el "alias"

private InputStream getInIfOpen() throws IOException { if (in == null) throw new IOException("Stream closed"); return in; }

  1. El hilo A llama a getInIfOpen()
  2. El hilo A evalúa in == null y ve que in no es null .
  3. El hilo B asigna null a null .
  4. El hilo A ejecuta el return in . Lo cual devuelve null porque a es volatile .

El "alias" evita esto. Ahora se lee solo una vez por el subproceso A. Si el subproceso B asigna un null después del subproceso A, no importa. El subproceso A generará una excepción o devolverá un valor no nulo (garantizado).