次のエディタでは、Java 命令の並べ替えの問題について簡単に説明します。編集者はこれがとても良いと思ったので、参考として共有します。エディターに従って見てみましょう。
命令の並べ替えは比較的複雑で、やや信じられないような問題です。また、例から始めます (実際に再現できるサンプルを実行することをお勧めします)。高)、感覚的に理解できました
/** * 一个简单的展示Happen-Before的例子. * 这里有两个共享变量:a和flag,初始值分别为0和false.在ThreadA中先给 a=1,然后flag=true. * 如果按照有序的话,那么在ThreadB中如果if(flag)成功的话,则应该a=1,而a=a*1之后a仍然为1,下方的if(a==0)应该永远不会为 * 真,永远不会打印. * 但实际情况是:在试验100次的情况下会出现0次或几次的打印结果,而试验1000次结果更明显,有十几次打印. */ public class SimpleHappenBefore { /** 这是一个验证结果的变量 */ private static int a=0; /** 这是一个标志位 */ private static boolean flag=false; public static void main(String[] args) throws InterruptedException { //由于多线程情况下未必会试出重排序的结论,所以多试一些次 for(int i=0;i<1000;i++){ ThreadA threadA=new ThreadA(); ThreadB threadB=new ThreadB(); threadA.start(); threadB.start(); //这里等待线程结束后,重置共享变量,以使验证结果的工作变得简单些. threadA.join(); threadB.join(); a=0; flag=false; } } static class ThreadA extends Thread{ public void run(){ a=1; flag=true; } } static class ThreadB extends Thread{ public void run(){ if(flag){ a=a*1; } if(a==0){ System.out.println("ha,a==0"); } } } }
例は比較的簡単で、コメントも追加されているので、詳細は説明しません。
命令の並べ替えとは何ですか? 2 つのレベルがあります:
仮想マシン レベルでは、メモリ動作速度が CPU 実行速度よりもはるかに遅いことによって引き起こされる CPU 空きの影響を最小限に抑えるために、仮想マシンは独自のルールのいくつかに従います (これらの規則については後で説明します) プログラムが書かれた順序を整理しないでください。つまり、後で書かれたコードが時間順に最初に実行され、先に書かれたコードが後で実行される可能性があります。これは、CPU を最大限に活用するためです。できるだけ。上記の例を見てみましょう: a=1 の操作ではなく、a=new byte[1024*1024] (1M スペースを割り当てます)` の場合、この時点で CPU は非常に遅く実行されます。それとも最初に次の flag=true を実行する必要がありますか?もちろん、最初に flag=true を実行すると、CPU が事前に使用され、全体の効率が向上します。もちろん、この前提条件はエラーが発生しないことです (どのようなエラーが発生するかについては後で説明します)。ここには 2 つの状況があります: 後のコードが前のコードより先に実行を開始する場合と、前のコードが最初に実行を開始しますが、効率が低い場合は、後のコードが前のコードの実行より前に実行を開始して終了する場合があります。どちらが先に開始しても、場合によっては以下のコードが先に終了する可能性があります。
ハードウェア レベルでは、CPU はそのルールに従って受信した命令のバッチを並べ替えます。これは、前の点の目的と同様に、CPU の速度がキャッシュの速度よりも速いという事実に基づいています。ただし、ハードウェア処理の場合は、受信した命令の限られた範囲内でのみ並べ替えで処理できますが、仮想マシンはより大きなレベルおよびより多くの命令範囲内で並べ替えることができます。ハードウェアの並べ替えメカニズムについては、「JVM 同時実行による CPU メモリの並べ替え」を参照してください。 並べ替えは非常に理解しにくいですが、この概念をよりよく理解するには、いくつかの例と図を作成する必要があります。ここでは、「説明の前に起こる」と「Java メモリ モデルを深く理解する (2) - 並べ替え」という 2 つの詳細かつ鮮明な記事を紹介します。このうち、「as-if-serial」、つまりシングルスレッドプログラムの実行結果はどんなに並べ替えても変更できないことを習得する必要があります。コンパイラー、ランタイム、プロセッサーはすべて、「as-if-serial」セマンティクスに従う必要があります。簡単な例を見てみましょう。
public void execute(){ int a=0; int b=1; int c=a+b; }
前の例からわかるように、マルチスレッド環境では並べ替えが行われる可能性が非常に高くなります。さらに、キーワード volatile と synchronized によって並べ替えが無効になる可能性があります。日々のプログラミング作業の中で、並べ替えのデメリットを感じています。
プログラム順序ルール: スレッド内では、コードの順序に従って、前に書かれた操作が後ろに書かれた操作より前に発生します。分岐やループなどの構造を考慮する必要があるため、正確にはコードシーケンスではなく制御フローシーケンスになります。
モニター ロック ルール: ロック解除操作は、同じオブジェクト ロックに対する後続のロック操作の前に発生します。ここで強調されているのは同じロックであり、「後で」とは、他のスレッドで発生するロック操作などの時間的シーケンスを指します。
揮発性変数ルール (Volatile Variable Rule): 揮発性変数の書き込み操作は、その後の変数の読み取り操作の後に行われます。ここでの「後で」とは、時間順序も指します。
スレッド開始ルール: スレッドの排他的な start() メソッドは、このスレッドのすべてのアクションに先行します。
スレッド終了ルール: スレッド内のすべての操作は、Thread.join() メソッドの終了と Thread.isAlive() 実行の終了によってスレッドを検出できます。 。
スレッド割り込みルール: スレッド割り込み() メソッドの呼び出しは、割り込みイベントの発生を検出するために、割り込まれたスレッドのコードよりも優先されます。スレッドが中断されたかどうかを検出するには、Thread.interrupted() メソッドを使用できます。中断されました。
オブジェクトの終了原則 (ファイナライザー ルール): オブジェクトの初期化 (コンストラクターの実行の終了) は、finalize() メソッドの開始時に最初に行われます。
推移性: 操作 A が操作 B の前に発生し、操作 B が操作 C の前に発生した場合、操作 A は操作 C の前に発生すると結論付けることができます。
happen-before の順序を保証するのは上記のルールです。上記のルールが満たされていない場合、マルチスレッド環境では、実行順序がコードの順序と等しいという保証はありません。このスレッドで観察された場合、すべての操作は正常です; 1 つのスレッドで別のスレッドを観察した場合、上記のルールを満たさないすべての操作は正常ではありません。」 したがって、マルチスレッド プログラムが順序に依存している場合は、コードを作成する際には、それが上記のルールに適合するかどうかを検討する必要があります。適合しない場合は、適合させるためにいくつかのメカニズムを使用する必要があります。最も一般的に使用されるのは、同期修飾子、ロック修飾子、および揮発性修飾子です。
以上がJava 命令の並べ替えの問題を解決するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。