Warum kopiert BufferedInputStream ein Feld in eine lokale Variable, anstatt das Feld direkt zu verwenden?

107

Wenn ich den Quellcode von lese java.io.BufferedInputStream.getInIfOpen(), bin ich verwirrt darüber, warum er Code wie diesen geschrieben hat:

/**
 * 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 wird der Alias ​​verwendet, anstatt die Feldvariable indirekt wie unten zu verwenden:

/**
 * 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?

Heilige
quelle
In Eclipsekönnen Sie einen Debugger für eine ifAnweisung nicht anhalten . Könnte ein Grund für diese Aliasvariable sein. Ich wollte das nur rauswerfen. Ich spekuliere natürlich.
Debosmit Ray
@DebosmitRay: Kann ich bei einer ifAussage wirklich nicht pausieren ?
Rkosegi
@rkosegi In meiner Version von Eclipse ähnelt das Problem diesem . Könnte nicht sehr häufig vorkommen. Und überhaupt, ich habe es nicht mit einer leichten Note gemeint (eindeutig ein schlechter Witz). :)
Debosmit Ray

Antworten:

119

Wenn Sie diesen Code außerhalb des Kontexts betrachten, gibt es keine gute Erklärung für diesen "Alias". Es ist einfach redundanter Code oder schlechter Codestil.

Der Kontext ist jedoch, dass BufferedInputStreames sich um eine Klasse handelt, die in Unterklassen unterteilt werden kann und in einem Multithread-Kontext arbeiten muss.

Der Hinweis ist, dass in inis deklariert FilterInputStreamist protected volatile. Das bedeutet , dass eine Chance besteht, dass eine Unterklasse in und assign erreichen konnte nullzu in. Angesichts dieser Möglichkeit ist der "Alias" tatsächlich vorhanden, um eine Rennbedingung zu verhindern.

Betrachten Sie den Code ohne den "Alias"

private InputStream getInIfOpen() throws IOException {
    if (in == null)
        throw new IOException("Stream closed");
    return in;
}
  1. Thread A ruft auf getInIfOpen()
  2. Thread A wertet aus in == nullund sieht das innicht null.
  3. Gewinde B Abtretungsempfänger nullzu in.
  4. Thread A wird ausgeführt return in. Was zurückkehrt, nullweil aa volatile.

Der "Alias" verhindert dies. Jetzt inwird nur einmal von Thread A gelesen. Wenn Thread B nullnach Thread A zuweistin , spielt dies keine Rolle. Thread A löst entweder eine Ausnahme aus oder gibt einen (garantierten) Wert ungleich Null zurück.

Stephen C.
quelle
11
Dies zeigt, warum protectedVariablen in einem Multithread-Kontext böse sind.
Mick Mnemonic
2
Das tut es tatsächlich. AFAIK Diese Klassen gehen jedoch bis auf Java 1.0 zurück. Dies ist nur ein weiteres Beispiel für eine schlechte Entwurfsentscheidung, die aus Angst vor einem Bruch des Kundencodes nicht behoben werden konnte.
Stephen C
2
@StephenC Danke für die ausführliche Erklärung +1. Bedeutet das also, dass wir keine protectedVariablen in unserem Code verwenden sollten, wenn dieser über mehrere Threads verfügt?
Madhusudana Reddy Sunnapu
3
@MadhusudanaReddySunnapu Die allgemeine Lektion lautet, dass Sie in einer Umgebung, in der mehrere Threads möglicherweise auf denselben Status zugreifen, diesen Zugriff irgendwie steuern müssen. Dies kann eine private Variable sein, auf die nur über den Setter zugegriffen werden kann. Es kann sich um einen lokalen Schutz wie diesen handeln, indem die Variable auf threadsichere Weise einmal geschrieben wird.
Chris Hayes
3
@sam - 1) Es müssen nicht alle Rennbedingungen und -zustände erklärt werden. Ziel der Antwort ist es aufzuzeigen, warum dieser scheinbar unerklärliche Code tatsächlich notwendig ist. 2) Wie so?
Stephen C
20

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

Hier sehen Sie die Deklaration von in, die in die übergeordnete Klasse gestellt wird FilterInputStream:

protected volatile InputStream in;

Da dies der protectedFall ist , kann sein Wert von jeder Unterklasse von FilterInputStreameinschließlich BufferedInputStreamund seinen Unterklassen geändert werden . Außerdem wird es deklariert volatile. Wenn also ein Thread den Wert der Variablen ändert, wird diese Änderung sofort in allen anderen Threads wiedergegeben. Diese Kombination ist schlecht, da die Klasse BufferedInputStreamnicht steuern oder wissen kann, wann sie ingeändert wird. Somit kann der Wert sogar zwischen der Prüfung auf Null und der return-Anweisung in geändert werden BufferedInputStream::getInIfOpen, wodurch die Prüfung auf Null effektiv unbrauchbar wird. Durch das ineinmalige Lesen des Werts zum Zwischenspeichern in der lokalen Variablen inputist die Methode BufferedInputStream::getInIfOpenvor Änderungen durch andere Threads geschützt, da lokale Variablen immer einem einzelnen Thread gehören.

Es gibt ein Beispiel in BufferedInputStream::close, das inauf null gesetzt ist:

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

Wenn BufferedInputStream::closees von einem anderen Thread aufgerufen wird, während BufferedInputStream::getInIfOpenes ausgeführt wird, würde dies zu der oben beschriebenen Race-Bedingung führen.

Stefan Dollase
quelle
Ich bin damit einverstanden, da wir Dinge wie das Sehen compareAndSet(), CASusw. im Code und in den Kommentaren. Ich habe auch den BufferedInputStreamCode durchsucht und zahlreiche synchronizedMethoden gefunden. Es ist also für die Verwendung mit mehreren Threads gedacht, obwohl ich es sicher noch nie so verwendet habe. Wie auch immer, ich denke deine Antwort ist richtig!
sparc_spread
Dies ist wahrscheinlich sinnvoll, da getInIfOpen()nur von public synchronizedMethoden von aufgerufen wird BufferedInputStream.
Mick Mnemonic
6

Dies ist ein so kurzer Code, der sich jedoch in einer Umgebung mit mehreren Threads theoretisch indirekt nach dem Vergleich ändern kann, sodass die Methode etwas zurückgeben kann, das sie nicht überprüft hat (sie kann zurückkehren nullund genau das tun, was sie beabsichtigt war) verhindern).

acdcjunior
quelle
Bin ich richtig , wenn ich sage , dass die Referenz in könnte zwischen der Zeit ändern Sie die Methode und die Rückgabe des Wertes nennen (in einer Multithread - Umgebung)?
Debosmit Ray
Ja, das kannst du sagen. Letztendlich wird die Wahrscheinlichkeit wirklich vom konkreten Fall abhängen (alles, was wir für eine Tatsache wissen, ist, dass insich dies jederzeit ändern kann).
acdcjunior
4

Ich glaube, das Erfassen der Klassenvariablen inin der lokalen Variablen inputsoll inkonsistentes Verhalten verhindern, wenn ines während der getInIfOpen()Ausführung von einem anderen Thread geändert wird.

Beachten Sie, dass der Eigentümer von indie übergeordnete Klasse ist und diese nicht als markiert final.

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

Sam
quelle