この例では、すべてがうまくいきます(うーん、判断を少し中断しましょう)。スレッドセーフに関しては、不変性はアンブロシアです。値を変更できない場合、同時実行性の問題の大部分はすぐには問題になりません。
Amirは、これは一般的に有用であると述べましたが、コンストラクターには、可視性を保証する変数volatile
の同様のセマンティクスもあります。final
詳細については、 JLSの17.5節を参照してください。基本的に、コンストラクターは、最終変数への書き込みと後続の読み取りの間に発生前の関係を形成します。
編集:コンストラクターで配列への値参照を設定すると、その時点ですべてのスレッドに表示され、その後は変更されません。したがって、他のすべてのスレッドが同じ配列を参照することがわかります。しかし、配列の内容はどうですか?
現状では、配列要素にはボラティリティに関して特別なセマンティクスはありません。まるで、クラスを自分で次のように宣言したかのようです。
public class ArrayTen {
private int _0;
private int _1;
// ...
private int _9;
public int get(int index) {
if (index == 0) return _0;
// etc.
}
}
つまり、別のスレッドは、発生前の関係を確立するために何かを実行できる場合にのみ、これらの変数を参照します。そして、私の理解が正しければ、これには元のコードにわずかな変更を加える必要があります。
配列参照の設定がコンストラクターの終了前に行われることはすでにわかっています。常に当てはまる追加のポイントは、1つのスレッドでのアクションが、同じスレッドでのその後のアクションの前に発生することです。したがって、最初に配列フィールドを設定し、次に最後のフィールドを割り当てることでこれらを組み合わせて、この推移的な可視性の保証を得ることができます。もちろん、これには一時変数が必要です。
public class ImmutableIntArray {
private final int[] array;
public ImmutableIntArray() {
int[] tmp = new int[10];
for (int i = 0; i < 10; i++) {
tmp[i] = i;
}
array = tmp;
}
// get() etc.
}
一見無関係に見える割り当てと人口の順序を切り替えたので、これは安全であることが保証されていると思います。
しかし、繰り返しになりますが、私が見逃したことがあるかもしれません。これは、並行性の保証が期待したほど堅牢ではないことを意味します。この質問は、非常に単純なことをしていると思っていても、防弾マルチスレッドコードを書くのが難しい理由と、正しく行うために多くの考えと注意(そしてバグ修正)が必要な理由の優れた例です。