Heim >Java >javaLernprogramm >Lösung des Problems der Neuordnung von Java-Anweisungen
Der folgende Editor bietet Ihnen eine kurze Diskussion zum Thema Neuordnung von Java-Anweisungen. Der Herausgeber findet es ziemlich gut, deshalb werde ich es jetzt mit Ihnen teilen und es allen als Referenz geben. Folgen wir dem Editor und schauen wir uns das an Die Anzahl der Nachbestellungen ist immer noch recht hoch.) Ich habe ein Wahrnehmungsverständnis
/** * 一个简单的展示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"); } } } }
Was ist eine Neuordnung von Anweisungen? Es gibt zwei Ebenen: Auf der Ebene der virtuellen Maschine, um die Auswirkungen von CPU-Leerständen zu minimieren, die dadurch verursacht werden, dass die Speicherbetriebsgeschwindigkeit viel langsamer ist als die CPU-Laufgeschwindigkeit, die virtuelle Maschine wird einigen seiner eigenen folgen Die Regel (diese Regel wird später beschrieben) stört die Reihenfolge, in der das Programm geschrieben wird – das heißt, der später geschriebene Code kann zuerst in chronologischer Reihenfolge ausgeführt werden, während der früher geschriebene Code später ausgeführt wird - um die CPU möglichst voll auszunutzen. Nehmen Sie das obige Beispiel: Wenn es sich nicht um die Operation „a=1“, sondern um „a=new byte[1024*1024] (1 MB Speicherplatz zuweisen)“ handelt, läuft die CPU zu diesem Zeitpunkt sehr langsam Ausführung zu beenden, oder sollten wir zuerst das folgende Flag=true ausführen? Offensichtlich kann die vorherige Ausführung von flag=true die CPU belasten und die Gesamteffizienz steigern. Diese Voraussetzung ist natürlich, dass keine Fehler auftreten (welche Art von Fehlern wird später besprochen). Obwohl es hier zwei Situationen gibt: Die Ausführung des späteren Codes beginnt vor der Ausführung des vorherigen Codes. Wenn die Effizienz jedoch langsam ist, beginnt die Ausführung des späteren Codes und endet vor der Ausführung des vorherigen Codes. Unabhängig davon, wer zuerst beginnt, besteht in einigen Fällen die Möglichkeit, dass der folgende Code zuerst endet.
Auf Hardwareebene ordnet die CPU den empfangenen Befehlsstapel gemäß ihren Regeln neu. Dies liegt auch daran, dass die CPU-Geschwindigkeit schneller ist als die Cache-Geschwindigkeit. Der Zweck ist dem vorherigen ähnlich Punkt, mit der Ausnahme, dass für die Hardwareverarbeitung jedes Mal nur innerhalb des begrenzten Bereichs der empfangenen Anweisungen neu angeordnet werden kann, während die virtuelle Maschine auf einer größeren Ebene und innerhalb eines größeren Bereichs von Anweisungen neu angeordnet werden kann. Informationen zum Hardware-Neuordnungsmechanismus finden Sie unter „CPU-Speicher-Neuordnung (Speicher-Neuordnung) durch JVM-Parallelität“
Die Neuordnung ist schwer zu verstehen. Wenn Sie dies besser verstehen möchten, werden die oben genannten Szenarien nur kurz erwähnt Das Konzept erfordert die Erstellung einiger Beispiele und Diagramme. Hier finden Sie zwei detailliertere und anschaulichere Artikel: „Erklärung vor dem Vorgehen“ und „Vertiefendes Verständnis des Java-Speichermodells (2) – Neuordnung“. Eines der „Als-ob-seriell“ sollte beherrscht werden, das heißt: Unabhängig von der Neuordnung kann das Ausführungsergebnis des Single-Thread-Programms nicht geändert werden. Der Compiler, die Laufzeit und der Prozessor müssen alle der „Als-ob-seriell“-Semantik entsprechen. Nehmen Sie ein einfaches Beispiel:
public void execute(){ int a=0; int b=1; int c=a+b; }
Wie Sie dem vorherigen Beispiel entnehmen können, ist die Wahrscheinlichkeit einer Neuordnung in einer Multithread-Umgebung recht hoch. Die Schlüsselwörter „volatil“ und „synchonized“ können die Neuordnung deaktivieren, und zwar diese Regeln, die verhindern, dass wir die Nachteile der Neuordnung in unserer täglichen Programmierarbeit spüren.
Programmreihenfolgeregel: Innerhalb eines Threads werden gemäß der Codereihenfolge die vorne geschriebenen Operationen vor den hinten geschriebenen Operationen ausgeführt. Genauer gesagt sollte es sich um die Kontrollflusssequenz und nicht um die Codesequenz handeln, da Strukturen wie Verzweigungen und Schleifen berücksichtigt werden müssen.
Sperrregel überwachen: Ein Entsperrvorgang erfolgt vor einem nachfolgenden Sperrvorgang für dieselbe Objektsperre. Der Schwerpunkt liegt hier auf derselben Sperre, und „später“ bezieht sich auf die zeitliche Reihenfolge, z. B. auf Sperrvorgänge, die in anderen Threads ausgeführt werden.
Regel für flüchtige Variablen: Ein Schreibvorgang auf eine flüchtige Variable erfolgt nach einem nachfolgenden Lesevorgang auf die Variable. Das „später“ bezieht sich hier auch auf die zeitliche Reihenfolge.
Thread-Startregel: Die exklusive start()-Methode des Threads geht jeder Aktion dieses Threads voraus.
Thread-Beendigungsregel: Jede Operation in einem Thread erfolgt zuerst bei der Beendigungserkennung dieses Threads. Wir können ihn über die Thread.join()-Methode und die Rückgabe von Thread.isAlive() beenden. Der Wert erkennt dies Der Thread hat die Ausführung beendet.
Thread-Unterbrechungsregel: Der Aufruf der Thread-Interrupte()-Methode hat Vorrang vor dem Code des unterbrochenen Threads, um das Auftreten des Interrupt-Ereignisses zu erkennen. Sie können die Thread.interrupted()-Methode verwenden, um zu erkennen, ob der Thread wurde unterbrochen.
Finalizer-Regel: Die Initialisierung eines Objekts (das Ende der Konstruktorausführung) erfolgt zuerst am Anfang seiner finalize()-Methode.
Transitivität: Wenn Operation A vor Operation B und Operation B vor Operation C erfolgt, können wir daraus schließen, dass Operation A vor Operation C erfolgt.
Es sind die oben genannten Regeln, die die Reihenfolge des Vorhergehens garantieren. Wenn die oben genannten Regeln nicht erfüllt sind, gibt es in einer Multithread-Umgebung keine Garantie dafür, dass die Ausführungsreihenfolge mit der Codereihenfolge übereinstimmt , „Wenn in diesem Thread beobachtet, sind alle Vorgänge in Ordnung; wenn Sie einen anderen Thread in einem Thread beobachten, ist alles, was die oben genannten Regeln nicht erfüllt, nicht in Ordnung.“ Daher ist unser Multithread-Programm auf die Reihenfolge angewiesen Beim Schreiben von Code müssen wir prüfen, ob er den oben genannten Regeln entspricht. Wenn die oben genannten Regeln nicht erfüllt sind, müssen einige Mechanismen verwendet werden, um sie konform zu machen. Die am häufigsten verwendeten sind synchronisierte, sperrende und flüchtige Modifikatoren.
Das obige ist der detaillierte Inhalt vonLösung des Problems der Neuordnung von Java-Anweisungen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!