Dlaczego BufferedInputStream kopiuje pole do zmiennej lokalnej, zamiast używać pola bezpośrednio

107

Kiedy czytam kod źródłowy java.io.BufferedInputStream.getInIfOpen(), nie rozumiem, dlaczego napisał on taki kod:

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

Dlaczego używa aliasu zamiast używać zmiennej pola inbezpośrednio, jak poniżej:

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

Czy ktoś może podać rozsądne wyjaśnienie?

Święty
źródło
W programie Eclipsenie można wstrzymać debugera na ifinstrukcji. Może być powodem tej zmiennej aliasu. Chciałem tylko to wyrzucić. Oczywiście spekuluję.
Debosmit Ray
@DebosmitRay: Naprawdę nie można wstrzymać ifwyciągu?
rkosegi
@rkosegi W mojej wersji Eclipse problem jest podobny do tego . Może nie być zbyt częstym zjawiskiem. Zresztą nie miałem tego na myśli w lekkim tonie (wyraźnie kiepski żart). :)
Debosmit Ray

Odpowiedzi:

119

Jeśli spojrzysz na ten kod z kontekstu, nie ma dobrego wyjaśnienia tego „aliasu”. Jest to po prostu nadmiarowy kod lub słaby styl kodu.

Ale kontekst jest taki, że BufferedInputStreamjest to klasa, która może być podklasą i że musi działać w kontekście wielowątkowym.

Wskazówka jest taka, że inzadeklarowano w FilterInputStreamis protected volatile. Oznacza to, że istnieje szansa, że ​​podklasa może dotrzeć do i przypisać nulldo in. Biorąc pod uwagę taką możliwość, „alias” faktycznie istnieje, aby zapobiec sytuacji wyścigu.

Rozważ kod bez „aliasu”

private InputStream getInIfOpen() throws IOException {
    if (in == null)
        throw new IOException("Stream closed");
    return in;
}
  1. Wątek wywołuje getInIfOpen()
  2. Wątek A ocenia in == nulli widzi, że intak nie jest null.
  3. Wątek B przypisuje nulldo in.
  4. Wątek wykonuje return in. Który powraca, nullponieważ ajest volatile.

„Alias” zapobiega temu. Teraz injest czytany tylko raz przez wątek A. Jeśli wątek B przypisuje się nullpo wątku A in, nie ma to znaczenia. Wątek A zgłosi wyjątek lub zwróci (gwarantowaną) wartość różną od null.

Stephen C.
źródło
11
Co pokazuje, dlaczego protectedzmienne są złe w kontekście wielowątkowym.
Mick Mnemonic
2
Naprawdę. Jednak AFAIK te klasy sięgają wstecz do Java 1.0. To kolejny przykład złej decyzji projektowej, której nie można było naprawić z obawy przed złamaniem kodu klienta.
Stephen C
2
@StephenC Dzięki za szczegółowe wyjaśnienie +1. Czy to oznacza, że ​​nie powinniśmy używać protectedzmiennych w naszym kodzie, jeśli jest on wielowątkowy?
Madhusudana Reddy Sunnapu
3
@MadhusudanaReddySunnapu Ogólna lekcja jest taka, że ​​w środowisku, w którym wiele wątków może uzyskać dostęp do tego samego stanu, musisz w jakiś sposób kontrolować ten dostęp. Może to być zmienna prywatna dostępna tylko za pośrednictwem ustawiacza, może to być lokalna ochrona, taka jak ta, może to być spowodowane jednorazowym zapisem zmiennej w sposób bezpieczny dla wątków.
Chris Hayes
3
@sam - 1) Nie trzeba wyjaśniać wszystkich warunków i stanów wyścigu. Celem odpowiedzi jest wskazanie, dlaczego ten pozornie niewytłumaczalny kod jest w rzeczywistości potrzebny. 2) Jak to?
Stephen C
20

Dzieje się tak, ponieważ klasa BufferedInputStreamjest przeznaczona do użytku wielowątkowego.

Tutaj widzisz deklarację in, która jest umieszczona w klasie nadrzędnej FilterInputStream:

protected volatile InputStream in;

Ponieważ tak jest protected, jego wartość może zostać zmieniona przez dowolną podklasę FilterInputStream, w tym BufferedInputStreami jej podklasy. Ponadto jest zadeklarowana volatile, co oznacza, że ​​jeśli którykolwiek wątek zmieni wartość zmiennej, ta zmiana zostanie natychmiast odzwierciedlona we wszystkich innych wątkach. Ta kombinacja jest zła, ponieważ oznacza, że ​​klasa BufferedInputStreamnie może kontrolować ani wiedzieć, kiedy inzostanie zmieniona. W związku z tym wartość można nawet zmienić między sprawdzeniem wartości null a instrukcją return in BufferedInputStream::getInIfOpen, co skutecznie sprawia, że ​​sprawdzanie wartości null jest bezużyteczne. Odczytując wartość intylko raz w celu buforowania jej w zmiennej lokalnej input, metoda BufferedInputStream::getInIfOpenjest zabezpieczona przed zmianami z innych wątków, ponieważ zmienne lokalne są zawsze własnością jednego wątku.

Oto przykład w programie BufferedInputStream::close, który ustawia inwartość 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()
    }
}

Jeśli BufferedInputStream::closejest wywoływana przez inny wątek podczas BufferedInputStream::getInIfOpenwykonywania, spowodowałoby to sytuację wyścigu opisaną powyżej.

Stefan Dollase
źródło
Zgadzam się, ponieważ widzimy takie rzeczy compareAndSet(), CASitp w kodzie iw komentarzach. Przeszukałem również BufferedInputStreamkod i znalazłem wiele synchronizedmetod. Jest więc przeznaczony do użytku wielowątkowego, chociaż z pewnością nigdy nie używałem go w ten sposób. W każdym razie myślę, że twoja odpowiedź jest prawidłowa!
sparc_spread
Prawdopodobnie ma to sens, ponieważ getInIfOpen()jest wywoływane tylko z public synchronizedmetod BufferedInputStream.
Mick Mnemonic
6

To jest taki krótki kod, ale teoretycznie w środowisku wielowątkowym inmoże się zmienić zaraz po porównaniu, więc metoda może zwrócić coś, czego nie sprawdzała (mogła zwrócić null, robiąc dokładnie to, do czego była przeznaczona zapobiec).

acdcjunior
źródło
Czy mam rację, jeśli powiem, że odniesienie in może się zmienić między momentem wywołania metody a zwróceniem wartości (w środowisku wielowątkowym)?
Debosmit Ray
Tak, możesz tak powiedzieć. Ostatecznie prawdopodobieństwo będzie naprawdę zależeć od konkretnego przypadku (jedyne, co wiemy, to fakt, że inmoże się zmienić w dowolnym momencie).
acdcjunior
4

Uważam, że przechwycenie zmiennej klasy indo zmiennej lokalnej inputma zapobiec niespójnemu zachowaniu, jeśli inzostanie zmieniony przez inny wątek podczas getInIfOpen()działania.

Zwróć uwagę, że właścicielem injest klasa nadrzędna i nie oznacza jej jako final.

Ten wzorzec jest powielany w innych częściach klasy i wydaje się być rozsądnym kodowaniem obronnym.

Sam
źródło