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

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


ではEclipseifステートメントでデバッガを一時停止することはできません。そのエイリアス変数の理由かもしれません。それをそこに捨てたかっただけです。もちろん、私は推測しています。
Debosmit Ray 2016

@DebosmitRay:ifステートメントで一時停止できないのですか?
rkosegi 2016年

@rkosegi私のバージョンのEclipseでは、問題はこれに似てます。あまり一般的ではないかもしれません。とにかく、私はそれを軽いメモでは意味しませんでした(明らかに悪いジョーク)。:)
Debosmit Ray 2016

回答:


119

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

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

手がかりはでin宣言されていることFilterInputStreamです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 == nullinそうでないことを確認しnullます。
  3. Bが割り当てるスレッドnullin
  4. スレッドAが実行されreturn inます。どちらを返すnullためaですvolatile

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


11
これはprotected、マルチスレッドのコンテキストで変数が悪である理由を示しています。
ミックニーモニック2016年

2
確かにそうです。ただし、これらのクラスは、Java 1.0にまで遡ります。これは、顧客のコードを壊すことを恐れて修正できなかった、不適切な設計決定のもう1つの例です。
スティーブンC

2
@StephenC詳細説明ありがとうございます+1。つまり、protectedマルチスレッド化されている場合、コードで変数を使用するべきではないということですか?
Madhusudana Reddy Sunnapu

3
@MadhusudanaReddySunnapu全体的な教訓は、複数のスレッドが同じ状態にアクセスする可能性がある環境では、そのアクセスを何らかの方法で制御する必要があるということです。これは、セッターを介してのみアクセス可能なプライベート変数である可能性があります。これは、このようなローカルガードである可能性があります。これは、変数をスレッドセーフな方法で1回だけ書き込むことによって可能です。
クリスヘイズ

3
@sam-1)すべての競合状態と状態を説明する必要はありません。答えの目的は、この一見不可解なコードが実際に必要理由を指摘することです。2)どうやって?
スティーブンC

20

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

ここでinは、親クラスに配置されたの宣言が表示されていFilterInputStreamます。

protected volatile InputStream in;

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

に例があります BufferedInputStream::closeinnullに設定にあります。

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 2016年

はのメソッドgetInIfOpen()からのみ呼び出されるため、これはおそらく意味がpublic synchronizedありBufferedInputStreamます。
ミックニーモニック2016年

6

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


(マルチスレッド環境で)メソッドを呼び出してから値が返されるまでの間に参照in 変更される可能性があると言っても私は正しいですか?
Debosmit Ray 2016

はい、あなたはそれを言うことができます。最終的には、可能性は実際の具体的なケースに依存します(事実は、inいつでも変わる可能性があることを知っています)。
acdcjunior 2016年

4

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.