2016-03-26 17 views
107

Wenn ich den Quellcode von java.io.BufferedInputStream.getInIfOpen() lesen, ich bin verwirrt darüber, warum es Code wie folgt geschrieben:Warum BufferedInputStream ein Feld auf eine lokale Variable kopieren, anstatt das Feld verwenden, um direkt

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

Warum ist es mit die Alias ​​stattdessen die Feldvariable der Verwendung in wie unten direkt:

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

Kann jemand eine vernünftige Erklärung geben?

+0

In 'Eclipse' können Sie einen Debugger nicht auf eine 'if'-Anweisung anhalten. * Könnte * ein Grund für diese Alias-Variable sein. Ich wollte das nur da rauswerfen. Ich spekuliere natürlich. –

+0

@DebosmitRay: Wirklich kann nicht auf 'if' Aussage pausieren? – rkosegi

+0

@rkosegi Bei meiner Version von Eclipse ist das Problem ähnlich wie bei [diesem] (http://stackoverflow.com/questions/20413287/bug-with-eclipse-debuggers-step-over). Vielleicht nicht ein sehr häufiges Vorkommnis. Und wie auch immer, ich meinte es nicht auf einer leichten Note (eindeutig ein schlechter Witz). :) –

Antwort

119

Wenn Sie diesen Code aus dem Zusammenhang betrachten, gibt es keine gute Erklärung für diesen "Alias". Es ist einfach redundanter Code oder schlechter Code-Stil.

Aber der Kontext ist, dass BufferedInputStream ist eine Klasse, die unterklassifiziert werden kann, und dass es in einem Multi-Thread-Kontext arbeiten muss.

Der Hinweis ist, dass in in FilterInputStream ist protected volatile deklariert ist. Das bedeutet, dass die Möglichkeit besteht, dass eine Unterklasse den Wert null bis in erreichen kann. In Anbetracht dieser Möglichkeit ist der "Alias" tatsächlich da, um eine Wettlaufsituation zu verhindern.

Betrachten Sie den Code ohne "alias"

private InputStream getInIfOpen() throws IOException { 
    if (in == null) 
     throw new IOException("Stream closed"); 
    return in; 
} 
  1. Thread A getInIfOpen()
  2. Thread A wertet ruft in == null und sieht, dass in nicht null ist.
  3. Gewinde B weist null zu in zu.
  4. Thread A führt return in aus. Was null zurückgibt, weil a ein volatile ist.

Der "Alias" verhindert dies. Jetzt wird in nur einmal nach Thread A gelesen. Wenn Thread B null zuweist, nachdem Thread A in hat, spielt es keine Rolle. Thread A wird entweder eine Ausnahme auslösen oder einen (garantierten) Nicht-Null-Wert zurückgeben.

+11

Das zeigt, warum 'geschützte' Variablen in einem Multithread-Kontext böse sind. –

+2

Es tut tatsächlich. AFAIK diese Klassen gehen jedoch den ganzen Weg zurück zu Java 1.0. Dies ist nur ein weiteres Beispiel für eine schlechte Design-Entscheidung, die nicht behoben werden konnte, aus Angst, den Kundencode zu brechen. –

+2

@StephenC Danke für die detaillierte Erklärung +1. Also heißt das, wir sollten in unserem Code keine "geschützten" Variablen verwenden, wenn es Multithread ist? –

20

Dies liegt daran, dass die Klasse BufferedInputStream für die Verwendung mit mehreren Threads ausgelegt ist.

Hier sehen Sie die Deklaration von in, die in der übergeordneten Klasse FilterInputStream platziert wird:

protected volatile InputStream in; 

Da es protected ist, kann ihr Wert durch eine Unterklasse von FilterInputStream geändert werden, einschließlich BufferedInputStream und ihren Unterklassen . Außerdem wird volatile deklariert, was bedeutet, dass, wenn ein Thread den Wert der Variablen ändert, diese Änderung sofort in allen anderen Threads widergespiegelt wird. Diese Kombination ist schlecht, da es bedeutet, dass die Klasse BufferedInputStream keine Möglichkeit hat, zu steuern oder zu wissen, wenn in geändert wird. Daher kann der Wert sogar zwischen der Überprüfung auf Null und der Rückgabeanweisung in BufferedInputStream::getInIfOpen geändert werden, wodurch die Überprüfung auf Null praktisch unbrauchbar wird.Wenn Sie den Wert in nur einmal lesen, um ihn in der lokalen Variablen input zwischenzuspeichern, ist die Methode BufferedInputStream::getInIfOpen sicher gegen Änderungen von anderen Threads, da lokale Variablen immer im Besitz eines einzelnen Threads sind.

Es ist ein Beispiel, in BufferedInputStream::close, die in auf null setzt:

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() 
    } 
} 

BufferedInputStream::close Wenn von einem anderen Thread aufgerufen wird, während BufferedInputStream::getInIfOpen ausgeführt wird, würde dies in der Race-Bedingung oben beschrieben führen.

+0

Ich stimme zu, insofern wir im Code und in den Kommentaren Dinge wie 'compareAndSet()', 'CAS', etc. sehen. Ich habe auch den 'BufferedInputStream' Code durchsucht und zahlreiche' synchronisierte' Methoden gefunden. Also, es ist für den Multi-Thread-Einsatz gedacht, obwohl ich es sicher nie so benutzt habe. Wie auch immer, ich denke deine Antwort ist richtig! –

+0

Dies ist wahrscheinlich sinnvoll, weil 'getInIfOpen()' nur von 'public synchronized' Methoden von' BufferedInputStream' aufgerufen wird. –

6

Dies ist ein so kurzer Code, aber theoretisch in einer Umgebung mit mehreren Threads, kann in direkt nach dem Vergleich ändern, so dass die Methode etwas zurückgeben konnte es nicht überprüfen hat (es könnte null zurückkehren, damit die tut genau, was es verhindern sollte).

+0

Bin ich richtig, wenn ich sage, dass sich die Referenz 'in' * möglicherweise * zwischen dem Zeitpunkt, an dem Sie die Methode aufrufen, und der Rückgabe des Wertes (in einer Multithread-Umgebung) ändert? –

+0

Ja, das kann man sagen. Letztlich hängt die Wahrscheinlichkeit wirklich vom konkreten Fall ab (alles was wir wissen ist, dass "in" sich jederzeit ändern kann). – acdcjunior

4

Ich glaube, die Erfassung der Klassenvariable in auf die lokale Variable input ist inkonsistentes Verhalten zu verhindern, wenn in Wechsel von einem anderen Thread ist während getInIfOpen() läuft.

Beachten Sie, dass der Besitzer von in die Elternklasse ist und nicht als final markiert.

Dieses Muster wird in anderen Teilen der Klasse repliziert und scheint eine vernünftige defensive Codierung zu sein.