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;
}
誰かが合理的な説明をすることはできますか?
このコードをコンテキスト外で見ると、その「エイリアス」の良い説明はありません。それは単に冗長なコードまたは貧弱なコードスタイルです。
しかし、コンテキストはBufferedInputStream
はサブクラス化できるクラスであり、マルチスレッドコンテキストで動作する必要があるということです。
手がかりは、in
がFilterInputStream
が_protected volatile
_で宣言されていることです。これは、サブクラスがnull
に到達してin
に割り当てる可能性があることを意味します。その可能性を考えると、「エイリアス」は実際には競合状態を防ぐためにあります。
「エイリアス」のないコードを検討してください
_private InputStream getInIfOpen() throws IOException {
if (in == null)
throw new IOException("Stream closed");
return in;
}
_
getInIfOpen()
を呼び出しますin == null
_を評価し、in
がnull
ではないことを確認します。null
をin
に割り当てます。return in
_を実行します。 null
はa
であるため、volatile
を返します。「エイリアス」はこれを防ぎます。スレッドAがin
を持っている後にスレッドBがnull
を割り当てる場合、in
はスレッドAによって一度だけ読み取られます。スレッドAは、例外をスローするか、(保証された)null以外の値を返します。
これは、クラスBufferedInputStream
がマルチスレッド用に設計されているためです。
ここに、親クラスin
に配置されているFilterInputStream
の宣言があります。
protected volatile InputStream in;
protected
であるため、その値は、FilterInputStream
とそのサブクラスを含む、BufferedInputStream
の任意のサブクラスによって変更できます。また、volatile
と宣言されています。つまり、スレッドが変数の値を変更すると、この変更は他のすべてのスレッドにすぐに反映されます。この組み合わせは、クラスBufferedInputStream
がin
が変更されたときを制御または知る方法がないことを意味するため、不適切です。したがって、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
が別のスレッドによって呼び出されると、上記の競合状態が発生します。
これは非常に短いコードですが、理論的には、マルチスレッド環境では、in
は比較の直後に変更される可能性があるため、メソッドはチェックしなかったものを返すことがあります(null
、したがって、予防することを意図した正確なことを行います)。
クラス変数in
をローカル変数input
にキャプチャすることは、getInIfOpen()
の実行中にin
が別のスレッドによって変更された場合の一貫性のない動作を防ぐためです。
in
の所有者は親クラスであり、final
としてマークしないことに注意してください。
このパターンはクラスの他の部分で複製され、合理的な防御コーディングのようです。