際どいプログラムを考えると、コンパイラー(具体的にはjvmの実装)は、遅延セット分析/スレッドエスケープ分析などを実行して、レースフリーにするために挿入する必要のあるフェンス命令を見つけますか?それとも、JITはどこで実行されるかに基づいてそれを行いませんか?
メモリフェンス命令は、アーキテクチャの命令セットに固有のものです。これらは、JVMの命令セット内の同等の命令ではありません。したがって、実際にプロセッサにフェンス命令を発行するのはJVM/JITです。
コンパイラがそれを行う場合(この場合はjvm)、コンパイラはとにかくそれをレースフリープログラムに変換するので、なぜ私たちは際どいプログラムを書くことができないのですか?そして、コンパイラーがそれを行う方法(フェンスの挿入によってレースフリーにする)の場合、Javaでの同時データ構造の実装のように、(意図的に)際どいプログラムをどのように書くことができますか?
コンパイラは、バイトコードを生成するときに、JVM内の変数に対して実行されるすべてのアクションがJavaメモリモデルで指定されたルールに従うことのみを保証します。具体的には、最適化の分野では、アクション間に存在しなければならない発生前の関係、またはアクション間の同期順序に影響を与えない限り、コンパイラーは任意の命令セットを自由に最適化できます。たとえば、コンパイラは揮発性変数の読み取りと書き込みを再編成しません。また、コードの保護された(同期された)領域に出入りするときに、発生前の関係が侵害されないようにします。
したがって、コンパイラが「際どい」プログラムをレースのないプログラムに変換するという記述は正しくありません。実際、(Javaメモリモデルではなく)レースフリーであると想定されるプログラムは、最適化後に「際どい」プログラムになる可能性があります。
Javaでのデータ構造の同時実装は、Javaメモリモデルによって提供される保証に依存しています。具体的には、これはJava 5から改訂されたJavaメモリモデルであり、揮発性変数の読み取りと書き込みの間の発生前の関係が正確に指定されています。のConcurrentXXXクラスjava.util.concurrent
パッケージは、揮発性読み取りのこの約束された動作に大きく依存して、レースのない動作を保証します。Javaメモリモデルでは、揮発性変数への書き込みは、それがプログラムの順序である場合、読み取りの前に発生することが保証されています。簡単に言うと、揮発性読み取りは常に変数内の最も正確なバージョンのデータを取得します。並行クラスはこれを利用して、データ構造を単一のスレッドで更新しながら、他の複数のスレッドで読み取ることができるようにします(他のシナリオでは、競合状態が発生します)。
または、jvm自体がレーシーをレースフリープログラムに変換しないという3番目の可能性がありますが、それを実行できる他の分析が存在します。本当ですか?
JVMはメモリフェンス命令を発行します。「レーシー」プログラムから「レースフリー」プログラムへの変換は実行されません。コンパイラがJavaメモリモデルに従うバイトコードを生成した場合、JVM / JITは、必要に応じてメモリフェンス命令を発行します。揮発性変数の読み取り/書き込み、オブジェクトのモニターの取得または解放などです。
繰り返すリスクを冒して、JVMもコンパイラも「際どい」プログラムをレースフリープログラムに変換したり、その逆を行ったりすることはありません。これとは逆の動作は、JavaメモリモデルまたはJVMのいずれかのバグです。プログラムの順序、同期の順序、および発生前の順序を理解することにより、プログラムを競合のないものとして作成する必要があります。コンパイラとJVMは、実行時に確実に実行されることを保証します。
JVMがメモリフェンス命令を発行し、Javaメモリモデルによって約束されることを保証する方法の詳細については、InfoQでこの記事を読むことをお勧めします。