46

JSR-133 FAQは次のように述べています。

しかし、同期には相互排除以上のものがあります。同期により、同期ブロックの前または最中のスレッドによるメモリ書き込みが、同じモニターで同期する他のスレッドに予測可能な方法で表示されるようになります。同期ブロックを終了した後、モニターを解放します。これにより、キャッシュがメイン メモリにフラッシュされ、このスレッドによって行われた書き込みが他のスレッドから見えるようになります。同期ブロックに入る前に、モニターを取得します。これにより、ローカル プロセッサのキャッシュが無効になり、変数がメイン メモリから再ロードされます。その後、以前のリリースで可視化されたすべての書き込みを確認できます。

また、最新の Sun VM では、非競合同期は低コストであるという記事を読んだことも覚えています。私はこの主張に少し混乱しています。次のようなコードを検討してください。

class Foo {
    int x = 1;
    int y = 1;
    ..
    synchronized (aLock) {
        x = x + 1;
    }
}

x への更新には同期が必要ですが、ロックを取得すると y の値もキャッシュからクリアされますか? もしそれが本当なら、ロックストライピングのような技術は役に立たないかもしれないからです。あるいは、JVM はコードを確実に分析して、同じロックを使用して別の同期ブロックで y が変更されていないことを確認し、同期ブロックに入るときに y の値をキャッシュにダンプしないようにすることはできますか?

4

6 に答える 6

46

簡単に言えば、JSR-133 の説明は行き過ぎているということです。JSR-133 は、言語または JVM 標準の一部ではない非規範的なドキュメントであるため、これは深刻な問題ではありません。むしろ、これは、メモリ モデルを実装するのに十分であるが、一般的に必要ではない 1 つの考えられる戦略を説明するドキュメントに過ぎません。その上、「キャッシュ フラッシュ」に関するコメントは、基本的に完全に場違いです。基本的にゼロ アーキテクチャは、任意のタイプの「キャッシュ フラッシュ」を実行することによって Java メモリ モデルを実装するためです (多くのアーキテクチャにはそのような命令さえありません)。

Java メモリ モデルは、可視性、原子性、先行発生関係などの観点から正式に定義されており、正確に (数学的に) 定義されモデル。正式に定義されていない動作はランダムであるか、一部のハードウェアおよび JVM 実装で実際に明確に定義されている可能性があります。 JVM を作成し、ハードウェアのセマンティクスを十分に認識していない限り、最初から明確に定義されていました。

したがって、引用したテキストは、Javaが保証するものを正式に説明しているのではなく、メモリの順序付けと可視性の保証非常に弱い架空のアーキテクチャが、キャッシュフラッシュを使用してJavaメモリモデルの要件を満たす方法を説明しています。キャッシュ フラッシュ、メイン メモリなどに関する実際の議論は、これらの概念が抽象言語およびメモリ モデルの仕様に存在しないため、一般的に Java に適用できないことは明らかです。

実際には、メモリモデルによって提供される保証は、完全なフラッシュよりもはるかに弱いです.アトミック、同時実行関連、またはロック操作のすべてでキャッシュ全体をフラッシュすると、法外にコストがかかります.これは実際にはほとんど行われません. むしろ、特別なアトミック CPU 操作が使用され、場合によってはメモリ バリア命令と組み合わせて使用​​され、メモリの可視性と順序付けを保証します。そのため、安価な競合のない同期と「キャッシュの完全なフラッシュ」との間の明らかな不一致は、1 つ目は true で 2 つ目はそうではないことに注意することで解決されます。Java メモリ モデルでは完全なフラッシュは必要ありません (実際にはフラッシュは発生しません)。

正式なメモリ モデルを理解するには少し重すぎる場合 (あなただけではありません)、Doug Lea のクックブック(実際には JSR-133 FAQ にリンクされています) を参照して、このトピックをさらに深く掘り下げることもできます。ただし、コンパイラの作成者を対象としているため、具体的なハードウェアの観点から問題になります。そこでは、同期を含む特定の操作に必要なバリアについて正確に説明しています。そこで説明されているバリアは、実際のハードウェアに非常に簡単にマッピングできます。実際のマッピングの多くは、クックブックで説明されています。

于 2009-12-07T23:24:06.607 に答える
10

BeeOnRopeは正しいです。引用するテキストは、Javaメモリモデルが実際に保証するものよりも、一般的な実装の詳細を詳しく掘り下げています。実際には、xで同期すると、yが実際にCPUキャッシュから削除されることがよくあります(また、例のxが揮発性変数である場合、効果をトリガーするために明示的な同期は必要ありません)。これは、ほとんどのCPU(これはハードウェア効果であり、JMMが説明するものではないことに注意してください)では、キャッシュはキャッシュラインと呼ばれるユニットで機能するためです。キャッシュラインは通常、マシンワードよりも長くなります(たとえば、64バイト幅)。キャッシュにロードまたは無効化できるのは完全な行のみであるため、xとyが同じ行に分類され、一方をフラッシュするともう一方もフラッシュされる可能性が高くなります。

この効果を示すベンチマークを書くことが可能です。2つのvolatileintフィールドだけでクラスを作成し、2つのスレッドにいくつかの操作(たとえば、長いループでのインクリメント)を実行させます。1つはフィールドの1つで、もう1つはもう1つです。操作の時間を計ります。次に、2つの元のフィールドの間に16個のintフィールドを挿入し、テストを繰り返します(16 * 4 = 64)。配列は単なる参照であるため、16個の要素の配列ではうまくいきません。一方のフィールドでの操作がもう一方のフィールドに影響を与えなくなるため、パフォーマンスが大幅に向上する場合があります。これが機能するかどうかは、JVMの実装とプロセッサアーキテクチャによって異なります。これは、Sun JVMと一般的なx64ラップトップで実際に見たことがありますが、パフォーマンスの違いは数倍でした。

于 2012-02-22T20:45:53.417 に答える
7

x への更新には同期が必要ですが、ロックを取得すると y の値もキャッシュからクリアされますか? もしそれが本当なら、ロックストライピングのような技術は役に立たないかもしれないからです。

よくわかりませんが、答えはおそらく「はい」だと思います。このことを考慮:

class Foo {
    int x = 1;
    int y = 1;
    ..
    void bar() {
        synchronized (aLock) {
            x = x + 1;
        }
        y = y + 1;
    }
}

現在、このコードは、プログラムの残りの部分で何が起こるかによって、安全ではありません。ただし、ロック取得時の「実際の」値よりも古い値であってはならないyというのがメモリモデルの考え方だと思います。これは、と同様barにキャッシュを無効にする必要があることを意味します。yx

また、JVM はコードを確実に分析して、同じロックを使用する別の同期ブロックで y が変更されていないことを確認できますか?

ロックが の場合this、この分析は、すべてのクラスがプリロードされた後のグローバルな最適化として実現可能であるように見えます。(簡単だとか、価値があると言っているのではありません...)

より一般的なケースでは、特定のロックが特定の「所有」インスタンスに関連してのみ使用されることを証明する問題は、おそらく扱いにくいものです。

于 2009-12-05T00:14:15.283 に答える
4

jdk6.0のドキュメントhttp://java.sun.com/javase/6/docs/api/java/util/concurrent/package-summary.html#MemoryVisibilityを確認することをお勧めします

メモリ整合性プロパティJava言語仕様の第17章では、共有変数の読み取りや書き込みなどのメモリ操作の発生前の関係を定義しています。あるスレッドによる書き込みの結果は、読み取り操作の前に、書き込み操作が発生した場合にのみ、別のスレッドによる読み取りに表示されることが保証されます。同期された揮発性の構造、およびThread.start()メソッドとThread.join()メソッドは、発生前の関係を形成できます。特に:

  • スレッド内の各アクションは、プログラムの順序の後半にあるそのスレッド内のすべてのアクションの前に発生します。
  • モニターのロック解除(同期ブロックまたはメソッド終了)が発生します-同じモニターの後続のすべてのロック(同期ブロックまたはメソッド入力)の前に。また、発生前の関係は推移的であるため、ロック解除前のスレッドのすべてのアクションは、そのモニターのスレッドロック後のすべてのアクションの前に発生します。
  • 揮発性フィールドへの書き込みは、同じフィールドの後続のすべての読み取りの前に発生します。揮発性フィールドの書き込みと読み取りには、モニターの出入りと同様のメモリ整合性の効果がありますが、相互排他ロックは必要ありません。
  • スレッドで開始するための呼び出しが発生します-開始されたスレッドでのアクションの前に。
  • スレッド内のすべてのアクションが発生します-他のスレッドがそのスレッドの結合から正常に戻る前に

したがって、上記の強調表示されたポイントで述べたように、モニターでロック解除が発生する前に発生するすべての変更は、同じモニターでロックを取得するすべてのスレッド(および独自の同期ブロック)に表示されます。これは、Javaの発生に準拠しています。 -セマンティクスの前。したがって、他のスレッドが「aLock」でモニターを取得すると、yに加えられたすべての変更もメインメモリにフラッシュされます。

于 2013-02-27T22:48:19.103 に答える
1

同期は、1 つのスレッドのみがコード ブロックに入ることができることを保証します。ただし、同期セクション内で行われた変数の変更が他のスレッドに表示されることは保証されません。同期ブロックに入るスレッドだけが、変更を確認できることが保証されます。Java における同期のメ​​モリ効果は、c++ に関するダブルチェック ロックの問題と比較することができます。Java ダブルチェック ロックは、マルチスレッド環境で遅延初期化を実装するための効率的な方法として広く引用され、使用されています。残念ながら、Java で実装した場合、プラットフォームに依存しない方法では確実に動作しません。、追加の同期なし。C++ などの他の言語で実装された場合、プロセッサのメモリ モデル、コンパイラによって実行される並べ替え、およびコンパイラと同期ライブラリ間の相互作用に依存します。これらはいずれも C++ などの言語で指定されていないため、動作する状況についてはほとんど言えません。明示的なメモリ バリアを使用して C++ で動作させることができますが、これらのバリアは Java では使用できません。

于 2010-08-18T20:22:51.980 に答える