JSR 133 クックブックを読んでいて、メモリバリアについて次の質問があります。挿入されたメモリバリアの例は本にありますが、ローカル変数からの書き込みと読み取りのみが使用されます。次の変数があるとします
int a;
volatile int b;
そしてコード
b=a;
この1行で次の指示が生成されることを正しく理解していますか?
load a
LoadStore membar
store b
JSR 133 クックブックを読んでいて、メモリバリアについて次の質問があります。挿入されたメモリバリアの例は本にありますが、ローカル変数からの書き込みと読み取りのみが使用されます。次の変数があるとします
int a;
volatile int b;
そしてコード
b=a;
この1行で次の指示が生成されることを正しく理解していますか?
load a
LoadStore membar
store b
JVMの基本的な動作は、揮発性変数に対してのみ保証されます。b = a;
スレッドがステートメントの評価を完了した後でも、2つの別々のスレッドが変数「a」の異なる値にアクセスできる可能性があります。JVMは、揮発性変数へのアクセスがシリアル化され、Happens-Beforeセマンティクスを備えていることのみを保証します。これが意味するのは、2つの異なるスレッドで実行b = a;
した結果(「a」(ha ha)の「揮発性」値に直面して)は不確定です。これは、JVMが「b」へのストアがシリアル化されているとだけ言っているためです。どのスレッドが優先されるかは保証されません。
より正確には、これが意味するのは、JVMが変数'b'を独自のロックを持つものとして扱うということです。一度に1つのスレッドのみが「b」の読み取りまたは書き込みを許可します。このロックは、「b」へのアクセスのみを保護し、他には何も保護しません。
これは、JVMが異なれば異なることを意味し、このロックが実際に異なるマシンアーキテクチャに実装されると、アプリケーションの実行時の動作が大幅に異なる可能性があります。信頼できる唯一の保証は、Javaリファレンスマニュアルに「フィールドは揮発性として宣言される可能性があります。その場合、Javaメモリモデルはすべてのスレッドが変数の一貫した値を参照することを保証します」と述べています。詳細なレビューについては、さまざまなJVM実装がこの問題にどのように対処するかの例について、DennisByrneの優れた記事を参照してください。
Happens-Beforeセマンティクスは、提供された例ではあまり興味深いものではありません。整数プリミティブは、volatileが(部分的に)修正することを意図した種類の命令の並べ替えの機会をあまり提供しないためです。より良い例はこれです:
private AnObjectWithAComplicatedConstructor _sampleA;
private volatile AnObjectWithAComplicatedConstructor _sampleB;
public void getSampleA() {
if (_sampleA == null) {
_sampleA = new AnObjectWithAComplicatedConstructor();
}
return _sampleA;
}
public void getSampleB() {
if (_sampleB == null) {
_sampleB = new AnObjectWithAComplicatedConstructor();
}
return _sampleB;
}
この例では、フィールド'_sampleA'に重大な問題があります。マルチスレッドの状況では、「_ sampleA」が1つのスレッドで初期化されると同時に、別のスレッドがそれを使用しようとすると、あらゆる種類の散発的で非常に複製が難しいバグが発生する可能性があります。これを確認するには、スレッドXがgetSampleA()でnewの「new」バイトコード命令ステートメントを実行し、(まだ初期化されていない)結果をフィールド「_sampleA」に格納することを検討してください。スレッドXはJVMによって一時停止され、スレッドYはgetSampleA()の実行を開始し、「_sampleA」がnullではないことを確認します。その後、初期化されていない値が返され、スレッドYは結果のインスタンスでメソッドの呼び出しを開始し、あらゆる種類の問題を引き起こします。もちろん、これは本番環境で、奇数時間にのみ表示されます。
フィールド_sampleBの最悪のケースは、個々のインスタンスを初期化する複数のスレッドがある可能性があることです。1つを除いてすべてが最終的に破棄されます。このようなコードは「同期された」ブロックでラップする必要がありますが、「_ sampleB」に最終的に格納された値がHappens-Beforeセマンティクスである必要があるため、volatileキーワードでうまくいきます。つまり、等号の右側にあるものは次のようになります。等号の左側にあるものが実行されると、完全であることが保証されます。