Чому BufferedInputStream копіює поле в локальну змінну, а не використовує поле безпосередньо


107

Коли я читаю вихідний код із java.io.BufferedInputStream.getInIfOpen(), я плутаюсь, чому він написав такий код:

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

Чому він використовується псевдонімом замість inпрямої змінної поля, як нижче:

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

Чи може хтось дати розумне пояснення?


У програмі Eclipseне можна призупинити налагоджувач у ifзаяві. Можливо, це буде причиною цієї змінної псевдоніму. Просто хотів це викинути. Думаю, звичайно.
Debosmit Ray

@DebosmitRay: Невже не можна призупинити ifзаяву?
rkosegi

@rkosegi У моїй версії Eclipse проблема схожа на цю . Це може бути не дуже поширеним явищем. І все одно, я не мав на увазі це з легкої нотки (явно поганий жарт). :)
Debosmit Ray

Відповіді:


119

Якщо ви дивитесь на цей код поза контекстом, для цього "псевдоніма" немає хорошого пояснення. Це просто зайвий код або поганий стиль коду.

Але контекст - BufferedInputStreamце клас, який можна підкласифікувати, і йому потрібно працювати в багатопотоковому контексті.

Підказка в тому, що inзаявлено в FilterInputStreamє protected volatile. Це означає, що є ймовірність, до якої підклас міг би звернутися і призначити nullйого in. Враховуючи таку можливість, "псевдонім" насправді є для запобігання стану гонки.

Розглянемо код без "псевдоніма"

private InputStream getInIfOpen() throws IOException {
    if (in == null)
        throw new IOException("Stream closed");
    return in;
}
  1. Нитка дзвінків getInIfOpen()
  2. Нитка А оцінює in == nullі бачить, що inце не так null.
  3. Тема B правонаступників nullдо in.
  4. Нитка А виконується return in. Який повертається, nullтому що aє volatile.

"Псевдонім" цьому заважає. Тепер inчитається лише один раз за темою A. Якщо нитка B призначається nullпісля потоку A, inце не має значення. Нитка A або кине виняток, або поверне (гарантоване) ненулеве значення.


11
Що показує, чому protectedзмінні є злими в багатопотоковому контексті.
Мік Мнемонічний

2
Це дійсно так. Однак AFAIK ці класи сягають назад до Java 1.0. Це лише ще один приклад поганого дизайнерського рішення, яке не вдалося виправити, побоюючись порушити код клієнта.
Стівен C

2
@StephenC Дякую за детальне пояснення +1. Так це означає, що ми не повинні використовувати protectedзмінні в нашому коді, якщо він є багатопотоковим?
Мадхусудана Редді Суннапу

3
@MadhusudanaReddySunnapu Загальний урок полягає в тому, що в середовищі, де декілька потоків можуть отримати доступ до одного стану, вам потрібно якось контролювати цей доступ. Це може бути приватна змінна, доступна лише через сеттер, це може бути локальна охорона, як це, це може бути шляхом внесення змінної запису один раз безпечним потоком.
Кріс Хейс

3
@sam - 1) Не потрібно пояснювати всі умови і стани гонки. Мета відповіді - вказати, чому цей, здавалося б, незрозумілий код насправді необхідний. 2) Як так?
Стівен C

20

Це тому, що клас BufferedInputStreamпризначений для багатопотокового використання.

Тут ви бачите декларацію in, яка розміщується в батьківському класі FilterInputStream:

protected volatile InputStream in;

Оскільки воно є protected, його значення може бути змінено будь-яким підкласом FilterInputStream, включаючи BufferedInputStreamі його підкласи. Крім того, це оголошено volatile, що означає, що якщо будь-який потік змінює значення змінної, ця зміна буде негайно відображена у всіх інших потоках. Ця комбінація погана, оскільки означає, що у класу BufferedInputStreamнемає способу контролювати чи знати, коли inвін змінюється. Таким чином, значення навіть може бути змінено між чеком на null та оператором return у BufferedInputStream::getInIfOpen, що фактично робить перевірку на null марною. Прочитавши значення inлише одного разу для кешування його в локальній змінній input, метод BufferedInputStream::getInIfOpenзахищений від змін з інших потоків, оскільки локальні змінні завжди належать одному потоку.

Є приклад в BufferedInputStream::close, який встановлює inнульове значення:

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викликається інший потік, поки BufferedInputStream::getInIfOpenвін виконується, це призведе до описаного вище стану гонки.


Я згоден, так як ми бачимо речі , як compareAndSet(), CASі т.д. в коді і в коментарях. Я також шукав BufferedInputStreamкод і знайшов численні synchronizedметоди. Отже, він призначений для багатопотокового використання, хоча я впевнений, що ніколи не використовував його таким чином. У всякому разі, я вважаю, що ваша відповідь правильна!
sparc_spread

Це, мабуть, має сенс, оскільки getInIfOpen()викликається лише public synchronizedметодами BufferedInputStream.
Мік Мнемонічний

6

Це такий короткий код, але теоретично в багатопотоковому середовищі він inможе змінитися відразу після порівняння, тому метод може повернути те, що він не перевірив (він міг повернутися null, зробивши таким чином те саме, що було призначено запобігти).


Чи правильно я скажу, що посилання in може змінюватися між часом виклику методу та поверненням значення (у багатопотоковому середовищі)?
Дебосміт Рей

Так, ви можете сказати це. Зрештою вірогідність дійсно буде залежати від конкретного випадку (все, що ми знаємо на факт, це те, що inможе змінитися в будь-який час).
acdcjunior

4

Я вважаю, що фіксація змінної класу inдо локальної змінної inputполягає у запобіганні непослідовної поведінки, якщо inвона змінюється іншим потоком під час getInIfOpen()запуску.

Зауважте, що власником inє батьківський клас і не позначає його як final.

Ця модель повторюється в інших частинах класу і, здається, є розумним оборонним кодуванням.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.