そもそもなぜそのようなルールが存在するのかを説明するのに役立つでしょう。
Javaは手続き型言語です。つまり、Javaに何かをする方法を教えます。Javaがあなたが書いた順序であなたの命令を実行しない場合、それは明らかに機能しません。たとえば、以下の例では、Javaが2-> 1-> 3を実行すると、シチューは台無しになります。
1. Take lid off
2. Pour salt in
3. Cook for 3 hours
では、なぜルールは単に「Javaはあなたが書いたものをあなたが書いた順序で実行する」と言っていないのですか?一言で言えば、Javaは賢いからです。次の例を見てください。
1. Take eggs out of the freezer
2. Take lid off
3. Take milk out of the freezer
4. Pour egg and milk in
5. Cook for 3 hours
Javaが私のようだったら、順番に実行するだけです。ただし、Javaは、より効率的であり、1-> 3-> 2-> 4-> 5を実行しても最終結果が同じになることを理解するのに十分賢いです(再び冷凍庫に行く必要はありません。それはレシピを変更しません)。
したがって、「スレッド内の各アクションが発生する-プログラムの順序の後半にあるそのスレッド内のすべてのアクションの前に」というルールが言おうとしているのは、「単一のスレッドでは、プログラムは正確に実行されたかのように実行されます。作成した順序。舞台裏で順序を変更する場合がありますが、出力が変更されないようにします。
ここまでは順調ですね。複数のスレッドで同じことをしないのはなぜですか?マルチスレッドプログラミングでは、Javaはそれを自動的に行うのに十分賢いわけではありません。一部の操作(スレッドの結合、スレッドの開始、ロック(モニター)が使用されている場合など)では機能しますが、その他の操作では、プログラム出力を変更するような並べ替えを行わないように明示的に指示する必要があります(volatile
フィールドのマーカーなど)。ロックの使用など)。
注:
「関係の前に起こる」に関する簡単な補遺。これは、Javaの並べ替えが何をする場合でも、AのものがBのものの前に発生するという素晴らしい言い方です。後の奇妙なシチューの例では、「ステップ1と3が発生します-ステップ4の前に「卵とミルクを注ぎます」」。また、たとえば、「ステップ1と3は相互に依存しないため、発生前の関係は必要ありません」
コメントへの追加の質問と回答について
まず、プログラミングの世界で「時間」が何を意味するかを確立しましょう。プログラミングでは、「絶対時間」(今の世界の時間は?)と「相対時間」(xからどれくらいの時間が経過したか)という概念があります。理想的な世界では、時は時ですが、原子時計が組み込まれていない限り、絶対時間は時々修正する必要があります。一方、相対的な時間については、イベント間の違いにのみ関心があるため、修正は必要ありません。
JavaではSystem.currentTime()
、絶対時間をSystem.nanoTime()
扱い、相対時間を扱います。これが、nanoTimeのJavadocが「この方法は経過時間を測定するためにのみ使用でき、システムや壁掛け時計の他の概念とは関係がない」と述べている理由です。
実際には、currentTimeMillisとnanoTimeはどちらもネイティブ呼び出しであるため、コンパイラーは、並べ替えが正確さに影響しないかどうかを実際に証明できません。つまり、実行が並べ替えられません。
しかし、実際にネイティブコードを調べて、合法である限りすべてを並べ替えるコンパイラ実装を作成したいとします。JLSを見ると、「検出できない限り、何でも並べ替えることができます」ということがわかります。コンパイラーの作成者として、並べ替えがセマンティクスに違反するかどうかを判断する必要があります。相対時間(nanoTime)の場合、実行を並べ替えると、明らかに役に立たなくなります(つまり、セマンティクスに違反します)。さて、絶対時間(currentTimeMillis)で並べ替えると、セマンティクスに違反しますか?世界の時刻のソース(たとえばシステム時計)と私たちが決定したもの(「50ms」など)*との差を制限できる限り、私はノーと言います。以下の例の場合:
long tick = System.currentTimeMillis();
result = compute();
long tock = System.currentTimeMillis();
print(result + ":" + tick - tock);
compute()
コンパイラが、許可できるシステムクロックからの最大発散よりも少ないことを証明できる場合は、次のようにこれを並べ替えることが合法です。
long tick = System.currentTimeMillis();
long tock = System.currentTimeMillis();
result = compute();
print(result + ":" + tick - tock);
そうすることで、定義した仕様に違反することはなく、したがってセマンティクスに違反することもありません。
また、なぜこれがJLSに含まれていないのかという質問もありました。答えは「JLSを短くする」だと思います。しかし、私はこの領域についてあまり知らないので、別の質問をすることをお勧めします。
*:実際の実装では、この違いはプラットフォームによって異なります。