VarHandleは、メモリロケーションアクセサをその上に適用されるメモリ順序セマンティクスから分離することによって、volatileアクセスを一般化します。volatile変数は、すべての読み取りおよび書き込みに対して常に完全な順序(逐次的一貫性)を強制しますが、VarHandleは、plain、opaque、acquire/release、およびvolatileの4つの異なるモードを提供し、完全な逐次的一貫性が不要な場合には弱い一貫性モデルを選択できるようにします。この分離は、高度な同時実行アルゴリズムがStoreLoadフェンスを回避できるようにし、x86やARMなどのアーキテクチャでスループットを大幅に改善します。例えば、単一生産者–単一消費者キューのようなシナリオでは、スループットを大幅に向上させることが可能です。このAPIは、sun.misc.Unsafeに頼ることなく、オフヒープアクセス、配列要素の操作、正確で検証可能なメモリセマンティクスでのレコードフィールドの更新のための完全サポートされた標準メカニズムを提供します。
我々は、プロデューサスレッドがイベントを書き込み、コンシューマスレッドがそれを処理する、テレメトリ取り込みで使用されるロックフリーのリングバッファを最適化しました。両者は共有バックアップ配列で動作します。初期実装では、バッファ要素のためにvolatile配列を使用しており、可視性を確保しつつ、各スロットの更新時に完全なメモリフェンスを発生させ、ARMベースのサーバーではボトルネックとなりました。
考慮された最初の代替案は、volatileを保持し、キャッシュラインパディングを追加して、フェイク共有を避けることでした。これにより正しさが保持され、キャッシュ整合性トラフィックが減少しましたが、volatileに固有の完全なStoreLoadバリアコストは依然として課され、プロデューサとコンシューマの間の順序保証に必要なCPUサイクルが無駄になっていました。
バッファインデックスを保護するsynchronizedブロックに戻すことを評価しましたが、これにより相互排除を提供することで安全性の推論が簡素化されました。しかし、残念ながら、このアプローチはプロデューサとコンシューマの操作を直列化し、サブミリ秒処理ターゲットにとって重要なロックフリーのレイテンシプロパティを破壊し、重い負荷の下で優先度反転のリスクを引き起こしました。
私たちは、プロデューサの書き込みに**setReleaseを、コンシューマの読み取りにgetAcquireを使用してVarHandle**を採用しました。このペアリングは、他の変数に対する完全な順序を強制することなく、書き込みとその後の読み取りの間の必要なハプンズ・ビフォー関係を提供し、単一生産者–単一消費者キューに必要なメモリモデルに完全に適合しました。
その結果、正しさを保ちながら、volatileベースラインと比較してARMサーバーでのスループットが約40%改善され、アルゴリズムデザインがすでに同時性パターンを制約している際には、弱い一貫性モデルが十分であることが示されました。
VarHandleは、オフヒープメモリにアクセスするためのUnsafeの単なる安全なラッパーですか?
VarHandleは、MemorySegmentを介してオフヒープセグメントを管理できますが、その主なアーキテクチャ的な進展は、Unsafeが不透明フェンスでおおよそ似せていたメモリ順序モードを公開することにあります。VarHandleは、アクセスが同期順序(acquire/release)に参加するか、単に原子性を提供するか(opaque)を宣言することを可能にします。この区別は、Unsafeの生のputOrderedが混同したり、手動のフェンス挿入を要求したりしたもので、JMMに対するコード検証をはるかに信頼性の高いものにします。
setOpaqueは、私の書き込みが最終的に他のスレッドに見えることを保証しますか?
いいえ。Opaqueモードは原子性と整合性を保証します—書き込みは不可分で、同じ変数への他の不透明なアクセスに対して順序づけられたものとして現れますが、スレッド間のハプンズ・ビフォー保証は提供されません。**getOpaque**で読み取るスレッドは、他の同期メカニズムがキャッシュフラッシュを強制しない限り、古いキャッシュ値を永遠に観察し続けることがあります。これとは異なり、acquire/releaseは書き手と読み手の間に必要な可視性エッジを作成します。
volatileモードをsetRelease/getAcquireよりも好むべき時はいつですか?
逐次的一貫性が必要な場合は、volatileを優先してください:グローバルな同期順序に対して、お互いのすべてのvolatile操作の完全な順序です。特定の書き込みとその後の読み取り(公開安全性)の間の順序を強制する必要がある場合は、acquire/releaseを使用してください。逐次的一貫性を仮定するアルゴリズムにacquire/releaseを誤って適用すると、独立した変数の更新が異なる観察者に対して順序を持たずに出現する微妙な再順序化バグが発生する可能性があります。