Javaで使用されるメモリフェンスとは何ですか?


18

バージョン9でJava SEに追加された新しいクラスSubmissionPublisherJava SE 10のソースコード、OpenJDK | docs)がどのように実装されているかを理解しようとしているVarHandleときに、以前は知らなかったいくつかのAPI呼び出しに遭遇しました。

fullFenceacquireFencereleaseFenceloadLoadFencestoreStoreFence

特にメモリバリア/フェンスの概念に関していくつかの調査を行った後(以前に聞いたことがありますが、使用していなかったため、セマンティクスにはまったく不慣れでした)、それらの目的について基本的な理解ができたと思います。それにもかかわらず、私の質問は誤解から生じる可能性があるため、私はそもそもそれが正しく行われたことを確認したいと思います。

  1. メモリバリアは、読み取りおよび書き込み操作に関する制約を並べ替えています。

  2. メモリバリアは、読み取りまたは書き込み、あるいはその両方に制約を設定するかどうかに応じて、単方向および双方向のメモリバリアという2つの主要なカテゴリに分類できます。

  3. C ++はさまざまなメモリバリアをサポートしていますが、これらはが提供するバリアとは一致しませんVarHandleただし、で利用可能なメモリバリアの一部は、対応するC ++メモリバリアと互換性のある順序付け効果VarHandle提供します。

    • #fullFence と互換性があります atomic_thread_fence(memory_order_seq_cst)
    • #acquireFence と互換性があります atomic_thread_fence(memory_order_acquire)
    • #releaseFence と互換性があります atomic_thread_fence(memory_order_release)
    • #loadLoadFenceそして、#storeStoreFence互換性のあるC ++カウンターパートを持っていません

詳細に関してはセマンティクスが明らかに異なるため、ここでは互換性という言葉は本当に重要なようです。たとえば、すべてのC ++バリアは双方向ですが、Javaのバリアはそうではありません(必須)。

  1. ほとんどのメモリバリアにも同期効果があります。これらは特に、使用されているバリアタイプと、他のスレッドで以前に実行されたバリア命令に依存します。バリア命令が持つ完全な影響はハードウェア固有なので、高レベル(C ++)のバリアを使用します。たとえば、C ++では、バリア解放命令の前に行われた変更は、バリア取得命令を実行するスレッドに表示されます。

私の仮定は正しいですか?もしそうなら、私の結果の質問は:

  1. で使用可能なメモリバリアはVarHandle、何らかの種類のメモリ同期を引き起こしますか?

  2. それらがメモリ同期を引き起こすかどうかに関係なく、Javaで何を並べ替える制約が役立つのでしょうか?Javaメモリモデルは、揮発性フィールド、ロック、またはVarHandle操作など#compareAndSetが関係する場合の順序付けに関して、すでにいくつかの非常に強力な保証を提供しています。

例を探している場合:前述BufferedSubscriptionSubmissionPublisher(上記のリンクされたソース)の内部クラスは、1079行に完全なフェンスを設定しました(関数growAndAdd;リンクされたWebサイトはフラグメント識別子をサポートしていないため、CTRL + Fを使用するだけです) )。しかし、それが何のためにあるのか私には分かりません。


1
私は答えようとしましたが、非常に簡単に言えば、Javaのモードよりも弱いモードが必要なために存在します。昇順では、次のようになりますplain -> opaque -> release/acquire -> volatile (sequential consistency)
ユージーン

回答:


11

これは主に無回答です(最初はコメントにしたかったのですが、ご覧のとおり長すぎます)。それは私がこれについて自分自身に多くの質問をし、多くの読書と研究をし、そして現時点で私は安全に言うことができるということです:これは複雑です。私も複数のテストを書いた jcstress(生成されたアセンブリコードを見ながら)、彼らがどのように動作するか本当に把握し、そのうちのいくつかは、一方で何とか意味を成し、一般の被写体は簡単されるものではありません。

最初に理解する必要があること:

Java言語仕様(JLS)では、バリアについては言及されていません。これは、Javaの場合、実装の詳細になります。セマンティクスの発生するという点で実際に機能します。JMM(Java Memory Model)に従ってこれらを適切に指定できるようにするには、を大幅に変更する必要があります。

これは進行中の作業です。

次に、ここで表面をスクラッチしたい場合これが一番最初に見るものです。話はすごい。私のお気に入りの部分は、Herb Sutterが5本の指を上げて「これは実際に正しく正しく操作できる人数です」と言うときです。これにより、関連する複雑さのヒントが得られます。それでも、把握しやすい簡単な例がいくつかあります(他のメモリ保証を気にせず、それ自体が正しくインクリメントされることだけを気にする複数のスレッドによって更新されるカウンターのように)。

別の例は、(Javaで)volatile停止/開始するスレッドを制御するフラグが必要な場合です。クラシカル:

volatile boolean stop = false; // on thread writes, one thread reads this    

Javaを使用している場合、このコードがない と機能しないことがわかりますvolatile(たとえば、ダブルチェックロックが機能しないと機能しない理由がわかります)。しかし、高性能のコードを書く一部の人にとって、これは多すぎることも知っていますか?volatile読み取り/書き込みは、シーケンシャルな一貫性も保証ます。これには強力な保証があり、一部の人々はこれのより弱いバージョンを望んでいます。

スレッドセーフフラグですが、揮発性ではありませんか?はい、正確に:VarHandle::set/getOpaque

そして、あなたはなぜ誰かが例えばそれを必要とするのか疑問に思うでしょう?誰もがに便乗されているすべての変更に興味があるわけではありませんvolatile

これをJavaでどのように実現するかを見てみましょう。まず、そのようなエキゾチックなものはすでにAPIに存在していましたAtomicInteger::lazySet。これはJavaメモリモデルでは規定されておらず、明確な定義はありません。まだ人々はそれを使っていました(LMAX、afaik、またはもっと読むためにこれ)。私見、AtomicInteger::lazySetですVarHandle::releaseFence(またはVarHandle::storeStoreFence)。


なぜ誰かがこれらを必要とするのか答えてみましょうか?

JMMには、基本的にフィールドにアクセスする2つの方法があります。プレーン揮発性です(これにより、順次一貫性が保証されます)。あなたが言及するこれらすべてのメソッドは、これらの2つの間に何かをもたらすためにあります- リリース/取得セマンティクスます。人が実際にいるケースがあると思いますこれを必要ます。

でも、もっとから緩和解除/獲得はなり不透明れ、私はまだ完全に理解しようとしています


したがって、ボトムライン(あなたの理解はかなり正しいです、ところで):これをJavaで使用することを計画している場合-現在、仕様はありませんが、あなた自身の責任でそれを行ってください。それらを理解したい場合は、C ++の同等のモードから始めることができます。


1
lazySet古代の回答にリンクすることでその意味を理解しようとしないでください。現在のドキュメントでは、現在の意味が正確に示されています。さらに、JMMには2つのアクセスモードしかないというのは誤解を招きます。我々は持っている揮発性の読み取りおよび揮発書き込み一緒に確立することができ、事前発生関係を。
Holger

1
私はそれについて何かを書いている最中でした。casは読み取りと書き込みの両方であり、完全な障壁のように機能することを考慮してください。たとえば、ロックを実装する場合、最初のアクションはロックカウントのcas(0、1)ですが、(揮発性読み取りのような)セマンティックの取得のみが必要ですが、ロックを解除するための0の最後の書き込みは(揮発性書き込みのような)リリースセマンティックを持つ必要があります)、したがって、ロック解除とその後のロックとの間には、前に発生があります。取得/解放は、異なるロックを使用するスレッドに関して、揮発性読み取り/書き込みよりもさらに弱いです。
Holger

1
@Peter Cordes:volatileキーワードのある最初のCバージョンは、Javaの5年後の C99でしたが、C ++ 03にもメモリモデルがないため、まだ有用なセマンティクスがありませんでした。C ++が「アトミック」と呼ぶものも、Javaよりもずっと新しいものです。また、volatileキーワードはアトミックな更新を意味するものではありません。では、なぜこのような名前にする必要があるのでしょうか。
Holger

1
@PeterCordesはと混同しているかもしれませんが、キーワードコンパイラ以外の拡張機能を使用するrestrictために作成__volatileする必要があったときのことを覚えています。おそらく、それはC89を完全に実装しなかったのでしょうか?私は私に教えてはいけないという古いです。Java 5より前volatileはCにかなり近かったが、JavaにはMMIOがなかったため、その目的は常にマルチスレッド化でしたが、Java 5より前のセマンティクスはそのためにあまり役に立ちませんでした。したがって、セマンティクスのようなリリース/取得が追加されましたが、それでもアトミックではありません(アトミックアップデートはその上に構築された追加機能です)。
Holger

2
@Eugene これは、私の例では、取得あろうロックするためのCASを使用するための特異的でした。カウントダウンラッチは、リリースセマンティックでアトミックデクリメントに耐え、スレッドがゼロに達して取得フェンスを挿入し、最終アクションを実行します。もちろん、完全なフェンスが必要なままのアトミック更新の場合もあります。
Holger
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.