web-dev-qa-db-ja.com

BufferedInputStreamがフィールドを直接使用するのではなく、フィールドをローカル変数にコピーする理由

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

誰かが合理的な説明をすることはできますか?

107
Saint

このコードをコンテキスト外で見ると、その「エイリアス」の良い説明はありません。それは単に冗長なコードまたは貧弱なコードスタイルです。

しかし、コンテキストはBufferedInputStreamはサブクラス化できるクラスであり、マルチスレッドコンテキストで動作する必要があるということです。

手がかりは、inFilterInputStreamが_protected volatile_で宣言されていることです。これは、サブクラスがnullに到達してinに割り当てる可能性があることを意味します。その可能性を考えると、「エイリアス」は実際には競合状態を防ぐためにあります。

「エイリアス」のないコードを検討してください

_private InputStream getInIfOpen() throws IOException {
    if (in == null)
        throw new IOException("Stream closed");
    return in;
}
_
  1. スレッドAはgetInIfOpen()を呼び出します
  2. スレッドAは_in == null_を評価し、innullではないことを確認します。
  3. スレッドBはnullinに割り当てます。
  4. スレッドAは_return in_を実行します。 nullaであるため、volatileを返します。

「エイリアス」はこれを防ぎます。スレッドAがinを持っている後にスレッドBがnullを割り当てる場合、inはスレッドAによって一度だけ読み取られます。スレッドAは、例外をスローするか、(保証された)null以外の値を返します。

119
Stephen C

これは、クラスBufferedInputStreamがマルチスレッド用に設計されているためです。

ここに、親クラスinに配置されているFilterInputStreamの宣言があります。

protected volatile InputStream in;

protectedであるため、その値は、FilterInputStreamとそのサブクラスを含む、BufferedInputStreamの任意のサブクラスによって変更できます。また、volatileと宣言されています。つまり、スレッドが変数の値を変更すると、この変更は他のすべてのスレッドにすぐに反映されます。この組み合わせは、クラスBufferedInputStreaminが変更されたときを制御または知る方法がないことを意味するため、不適切です。したがって、nullのチェックとBufferedInputStream::getInIfOpenのreturnステートメントの間で値を変更することさえできます。これにより、nullのチェックが事実上無効になります。 inの値を一度だけ読み取ってローカル変数inputにキャッシュすることにより、メソッドBufferedInputStream::getInIfOpenは他のスレッドからの変更に対して安全です。ローカル変数は常にシングルスレッド。

inをnullに設定するBufferedInputStream::closeの例があります。

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が別のスレッドによって呼び出されると、上記の競合状態が発生します。

20
Stefan Dollase

これは非常に短いコードですが、理論的には、マルチスレッド環境では、inは比較の直後に変更される可能性があるため、メソッドはチェックしなかったものを返すことがあります(null、したがって、予防することを意図した正確なことを行います)。

6
acdcjunior

クラス変数inをローカル変数inputにキャプチャすることは、getInIfOpen()の実行中にinが別のスレッドによって変更された場合の一貫性のない動作を防ぐためです。

inの所有者は親クラスであり、finalとしてマークしないことに注意してください。

このパターンはクラスの他の部分で複製され、合理的な防御コーディングのようです。

4
Sam