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

Lesezeit: 6 Minuten

Benutzer-Avatar
Heilige

Wenn ich den Quellcode auslese java.io.BufferedInputStream.getInIfOpen()ich bin verwirrt darüber, warum es 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 zu verwenden in direkt wie unten:

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

  • Im Eclipsekönnen Sie einen Debugger nicht anhalten if Aussage. Könnte ein Grund für diese Alias-Variable sein. Wollte das nur rauswerfen. Ich spekuliere natürlich.

    – Debosmit Ray

    26. März 2016 um 3:07 Uhr

  • @DebosmitRay: Kann wirklich nicht anhalten if Aussage?

    – rkosegi

    26. März 2016 um 15:39 Uhr

  • @rkosegi In meiner Version von Eclipse ist das Problem ähnlich wie dieses. Könnte nicht sehr häufig vorkommen. Und überhaupt, ich habe es nicht auf die leichte Schulter gemeint (eindeutig ein schlechter Witz). 🙂

    – Debosmit Ray

    27. März 2016 um 2:43 Uhr


Benutzer-Avatar
Stefan C

Wenn Sie sich diesen Code aus dem Kontext reißen, gibt es keine gute Erklärung für diesen “Alias”. Es ist einfach redundanter Code oder schlechter Codestil.

Aber der Kontext ist das BufferedInputStream ist eine Klasse, die in Unterklassen unterteilt werden kann und die in einem Kontext mit mehreren Threads funktionieren muss.

Der Hinweis ist das in deklariert ist FilterInputStream ist protected volatile. Das bedeutet, dass die Möglichkeit besteht, dass eine Unterklasse eingreifen und zuweisen kann null zu in. Angesichts dieser Möglichkeit ist der “Alias” eigentlich dazu da, eine Racebedingung 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 == null und sieht das in ist nicht null.
  3. Thread B weist zu null zu in.
  4. Thread A wird ausgeführt return in. Was zurückkehrt null Weil a ist ein volatile.

Der “Alias” verhindert dies. Jetzt in wird nur einmal von Thread A gelesen. Wenn Thread B zuweist null nachdem Thread A hat in es spielt keine Rolle. Thread A löst entweder eine Ausnahme aus oder gibt einen (garantierten) Nicht-Null-Wert zurück.

  • Was zeigt, warum protected Variablen sind in einem Multithread-Kontext böse.

    – Mick Mnemonik

    26. März 2016 um 3:23 Uhr

  • Das tut es in der Tat. AFAIK gehen diese Klassen jedoch bis zu Java 1.0 zurück. Dies ist nur ein weiteres Beispiel für eine schlechte Designentscheidung, die aus Angst, Kundencode zu brechen, nicht behoben werden konnte.

    – Stefan C

    26. März 2016 um 3:26 Uhr


  • @StephenC Danke für die ausführliche Erklärung +1. Bedeutet das also, wir sollten nicht verwenden protected Variablen in unserem Code, wenn es sich um Multithreading handelt?

    – Madhusudana Reddy Sunnapu

    26. März 2016 um 3:29 Uhr


  • @MadhusudanaReddySunnapu Die allgemeine Lehre lautet, dass Sie in einer Umgebung, in der mehrere Threads möglicherweise auf denselben Zustand zugreifen, diesen Zugriff irgendwie steuern müssen. Das kann eine private Variable sein, auf die nur über Setter zugegriffen werden kann, es könnte ein lokaler Wächter wie dieser sein, es könnte sein, dass die Variable auf Thread-sichere Weise einmal beschreibbar ist.

    – Chris Hayes

    26. März 2016 um 4:13 Uhr

  • @sam – 1) Es müssen nicht alle Rennbedingungen und Zustände erklärt werden. Das Ziel der Antwort ist es, darauf hinzuweisen, warum dies so ist scheinbar unerklärlich Code ist in der Tat notwendig. 2) Wieso?

    – Stefan C

    27. März 2016 um 1:08 Uhr

Benutzer-Avatar
Stefan Dollase

Das liegt an der Klasse BufferedInputStream ist für die Multithread-Nutzung ausgelegt.

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

protected volatile InputStream in;

Seit es ist protectedsein Wert kann von jeder Unterklasse von geändert werden FilterInputStreameinschließlich BufferedInputStream und seine Unterklassen. Außerdem ist es deklariert volatile, 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 sie die Klasse bedeutet BufferedInputStream hat keine Möglichkeit zu kontrollieren oder zu wissen, wann in ist geändert. Somit kann der Wert sogar zwischen der Prüfung auf null und der return-Anweisung in geändert werden BufferedInputStream::getInIfOpen, was die Überprüfung auf null effektiv nutzlos macht. Durch Lesen des Wertes von in nur einmal, um es in der lokalen Variablen zwischenzuspeichern inputdie Methode BufferedInputStream::getInIfOpen ist sicher gegen Änderungen von anderen Threads, da lokale Variablen immer einem einzelnen Thread gehören.

Ein Beispiel ist drin BufferedInputStream::closedie setzt in zu 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()
    }
}

Wenn BufferedInputStream::close wird dabei von einem anderen Thread aufgerufen BufferedInputStream::getInIfOpen ausgeführt wird, würde dies zu der oben beschriebenen Wettlaufbedingung führen.

  • Ich stimme zu, insofern wir Dinge sehen wie compareAndSet(), CAS, etc. im Code und in den Kommentaren. Ich habe auch die gesucht BufferedInputStream Code und zahlreiche gefunden synchronized Methoden. 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, Ihre Antwort ist richtig!

    – sparc_spread

    26. März 2016 um 3:07 Uhr


  • Dies ist wahrscheinlich sinnvoll, weil getInIfOpen() wird nur von angerufen public synchronized Methoden von BufferedInputStream.

    – Mick Mnemonik

    26. März 2016 um 3:11 Uhr

Dies ist ein so kurzer Code, aber theoretisch in einer Multithread-Umgebung in kann sich direkt nach dem Vergleich ändern, sodass die Methode etwas zurückgeben könnte, das sie nicht überprüft hat (sie könnte zurückgeben nullund tat damit genau das, was es verhindern sollte).

  • Bin ich richtig, wenn ich sage, dass die Referenz in könnte Änderung zwischen dem Aufrufen der Methode und der Rückgabe des Werts (in einer Multithread-Umgebung)?

    – Debosmit Ray

    26. März 2016 um 3:03 Uhr


  • Ja, das kann man sagen. Letztendlich wird die Wahrscheinlichkeit wirklich vom konkreten Fall abhängen (alles, was wir als Tatsache wissen, ist das in kann sich jederzeit ändern).

    – acdcjunior

    26. März 2016 um 3:14 Uhr

Ich glaube, die Klassenvariable zu erfassen in auf die lokale Variable input ist es, inkonsequentes Verhalten zu verhindern, wenn in wird zwar durch einen anderen Thread geändert getInIfOpen() läuft.

Beachten Sie, dass der Besitzer von in ist die übergeordnete Klasse und wird nicht als markiert final.

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

1298500cookie-checkWarum kopiert BufferedInputStream ein Feld in eine lokale Variable, anstatt das Feld direkt zu verwenden?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy