Heim  >  Artikel  >  Java  >  Detaillierte Einführung in die JAVA Virtual Machine (JVM) (8) – effiziente Parallelität

Detaillierte Einführung in die JAVA Virtual Machine (JVM) (8) – effiziente Parallelität

王林
王林nach vorne
2019-08-24 17:25:482366Durchsuche

Detaillierte Einführung in die JAVA Virtual Machine (JVM) (8) – effiziente Parallelität

Speichermodell

Das Speichermodell ist ein spezifisches Betriebsprotokoll für einen bestimmten Speicher oder Cache abstrahiert den Prozess für Lese- und Schreibzugriffe. Sein Hauptziel besteht darin, Zugriffsregeln für verschiedene Variablen im Programm zu definieren.

Hauptspeicher und Arbeitsspeicher

Detaillierte Einführung in die JAVA Virtual Machine (JVM) (8) – effiziente Parallelität

Alle Variablen werden im Hauptspeicher gespeichert. Sein Arbeitsspeicher ist eine Kopie des Hauptspeichers der vom Thread verwendeten Variablen Vorgänge wie Lesen und Zuweisen müssen im Arbeitsspeicher ausgeführt werden, Variablen im Hauptspeicher können nicht direkt gelesen werden.

Interaktionsoperationen zwischen dem Speicher

Kopieren vom Hauptspeicher in den Arbeitsspeicher: Führen Sie Lese- und Ladevorgänge nacheinander aus.
Arbeitsspeicher ist mit Hauptspeicher synchronisiert: Speicher- und Schreibvorgänge.

Eigenschaften von volatile

Volatile hat die gleiche Funktion wie synchronisiert, ist jedoch leichter als synchronisiert. Seine Hauptmerkmale sind die folgenden zwei Punkte:

Gewährleistung der Sichtbarkeit dieser Variable für alle Threads

Was bedeutet das? Das bedeutet, dass, wenn ein Thread den Wert dieser Variablen ändert, der neue Wert sofort anderen Threads bekannt ist. Gewöhnliche Variablen können dies nicht tun. Der Wert gewöhnlicher Variablen muss über den Hauptspeicher zwischen Threads übertragen werden. Thread A ändert beispielsweise den Wert einer gewöhnlichen Variablen und schreibt ihn dann in den Hauptspeicher zurück. Nachdem A das Zurückschreiben abgeschlossen hat und dann aus dem Hauptspeicher liest, ist der neue Variablenwert für Thread B sichtbar.

Optimierung der Befehlsneuordnung deaktivieren

Da die Befehlsneuordnung die gleichzeitige Ausführung des Programms beeinträchtigt.

Multi-Threading

Warum brauchen Sie Multithreading?

Die Lücke zwischen der Rechengeschwindigkeit des Computers und der Geschwindigkeit seines Speicher- und Kommunikationssubsystems ist zu groß und es wird viel Zeit für Festplatten-E/A, Netzwerkkommunikation usw. aufgewendet. und Datenbankzugriff. Durch die Verwendung von Multithreading kann die CPU besser ausgelastet werden.

Welche gleichzeitigen Anwendungsszenarien gibt es?

Nutzen Sie die Vorteile Ihres Computerprozessors voll aus

Ein Server stellt Dienste für mehrere Clients gleichzeitig bereit

So machen Sie den Prozessor intern Sind die Recheneinheiten voll ausgelastet?

Fügen Sie eine Cache-Ebene hinzu

Kopieren Sie die für den Vorgang erforderlichen Daten in den Cache, damit der Vorgang ausgeführt werden kann Mach es schnell. Wenn der Vorgang abgeschlossen ist, wird er aus dem Cache wieder mit dem Speicher synchronisiert, sodass der Prozessor nicht auf langsame Lese- und Schreibvorgänge im Speicher warten muss. Es gibt jedoch ein Problem zu berücksichtigen: Wie kann die Cache-Konsistenz sichergestellt werden?

Detaillierte Einführung in die JAVA Virtual Machine (JVM) (8) – effiziente Parallelität

Out-of-Order-Ausführung des Eingabecodes optimieren

Thread-Implementierung

Verwenden von Kernel-Threads zur Implementierung

Ein Kernel-Thread ist ein Thread, der direkt vom Betriebssystemkernel unterstützt wird.

Verwenden Sie Benutzer-Threads zur Implementierung

Die Einrichtung, Synchronisierung, Zerstörung und Planung von Benutzer-Threads werden im Benutzermodus ohne die Hilfe des Kernels vollständig abgeschlossen. und der Kernel nicht. Eine Implementierung, die sich der Existenz von Threads nicht bewusst ist. Diese Implementierung wird selten verwendet.

Benutzer-Threads plus Lightweight für Hybridimplementierung verwenden

Zusammengeführt

Thread-Planung

Thread-Planung bezieht sich auf den Prozess, durch den das System Threads Prozessornutzungsrechte zuweist. Es gibt zwei Haupttypen: kollaborativ und präventiv.

Kooperativ

Die Ausführungszeit des Threads wird vom Thread selbst gesteuert. Wenn der Thread die Ausführung seiner Arbeit beendet, benachrichtigt er das System aktiv, um zu einem anderen zu wechseln Faden.
Der Vorteil besteht darin, dass es einfach zu implementieren ist und es kein Problem mit der Thread-Synchronisierung gibt. Der Nachteil besteht darin, dass, wenn es ein Problem mit der Programmierung eines Threads gibt und das System nicht angewiesen wird, den Thread zu wechseln, das Programm dort immer blockiert wird, was leicht zum Absturz des Systems führen kann.

Präventiv

Threads wird vom System Ausführungszeit zugewiesen, und der Threadwechsel wird nicht von selbst bestimmt. Dies ist die von Java verwendete Thread-Planungsmethode.

Thread-Sicherheit

Wenn mehrere Threads auf ein Objekt zugreifen, wenn die Planung dieses Threads in der Laufzeitumgebung nicht berücksichtigt wird und Bei der alternativen Ausführung sind keine zusätzlichen Synchronisierungs- oder anderen Koordinierungsvorgänge für den Aufrufer erforderlich. Durch den Aufruf dieses Objekts kann das richtige Ergebnis erzielt werden, sodass dieses Objekt sicher ist.

Klassifizierung gemeinsamer Daten


Unveränderlich

Unveränderliche freigegebene Daten sind mit final geänderte Daten, die threadsicher sein müssen. Wenn es sich bei den gemeinsam genutzten Daten um eine Variable vom Basistyp handelt, verwenden Sie beim Definieren einfach das Schlüsselwort final.

Wenn es sich bei den gemeinsam genutzten Daten um ein Objekt handelt, darf das Verhalten des Objekts seinen Status nicht beeinflussen. Sie können alle Variablen mit Status im Objekt als endgültig deklarieren. Beispielsweise ist die String-Klasse eine unveränderliche Klasse

Absolut Thread-sicher

Markieren Sie sich als Thread-sichere Klasse in der Java-API. Die meisten davon sind nicht absolut Thread-sicher. Beispielsweise ist Vector eine Thread-sichere Sammlung, und alle ihre Methoden sind so geändert, dass sie synchronisiert werden können. In einer Umgebung mit mehreren Threads ist sie jedoch immer noch nicht synchronisiert.

Relative Thread-Sicherheit

Relative Thread-Sicherheit ist das, was wir normalerweise als Thread-Sicherheit bezeichnen garantieren, dass einzelne Operationen an diesem Objekt threadsicher sind. Bei bestimmten Abfolgen aufeinanderfolgender Anrufe kann es jedoch erforderlich sein, auf der Anruferseite zusätzliche Synchronisierungsmittel zu verwenden, um die Richtigkeit der Anrufe sicherzustellen.
Die meisten Thread-sicheren Klassen gehören zu diesem Typ.

Thread-kompatibel

Das Objekt selbst ist nicht linear sicher, kann aber durch korrekte Verwendung der Synchronisierung auf dem erstellt werden Aufrufseite Um sicherzustellen, dass Objekte in gleichzeitigen Umgebungen sicher verwendet werden können. Die meisten Klassen, die nicht threadsicher sind, fallen in diese Kategorie.

Thread-Opposition

Egal was passiert, es kann nicht gleichzeitig in einer Multithread-Umgebung verwendet werden, z System.setIn( ), System.SetOut(). Einer ändert die Eingabe und der andere ändert die Ausgabe. Die beiden können nicht „abwechselnd“ ausgeführt werden.

Implementierungsmethode

Methode 1: Sich gegenseitig ausschließende Synchronisation – pessimistische Parallelitätsstrategie

(1) synchronisiert

Das Prinzip lautet: Nach der Kompilierung befindet sich dieses Schlüsselwort im synchronisierten Block. Der Zwei-Byte-Code Die Anweisungen Monitorenter und Monitorexit werden vorher und nachher gebildet. Wenn die Monitorenter-Anweisung ausgeführt wird, versucht das Programm, die Sperre des Objekts zu erhalten. Wenn sie erworben werden kann, beträgt der Sperrenzähler +1. Wenn die Monitorexit-Anweisung ausgeführt wird, beträgt der Sperrenzähler entsprechend -1. Wenn der Zähler 0 erreicht, wird die Sperre aufgehoben.

Seine Eigenschaften sind: Es ist wiedereintrittsfähig für denselben Thread; der Synchronisationsblock blockiert den Eintritt anderer nachfolgender Threads, bevor der eingegebene Thread die Ausführung abschließt.

Das Anwendungsszenario lautet: Verwenden Sie dies nur, wenn es unbedingt erforderlich ist, da es ein Schwergewicht ist.

(2) ReentrantLock

Diese Wiedereintrittssperre ist eine Klasse unter dem Paket java.util.concurrent (JUC). Zu den erweiterten Funktionen gehören: Wartezeiten können unterbrochen werden, faire Sperren können implementiert werden und Sperren können an mehrere Bedingungen gebunden werden.

Methode 2: Nicht blockierende Synchronisierung – optimistische Parallelitätsstrategie

Führen Sie den Vorgang zuerst aus, wenn keine anderen Threads konkurrieren Bei gemeinsam genutzten Daten ist der Vorgang erfolgreich. Wenn ein Konflikt um die gemeinsam genutzten Daten besteht, werden andere Ausgleichsmaßnahmen ergriffen.

Methode 3: Keine Synchronisierungslösung

Wenn a Da bei der Methode kein Datenaustausch erfolgt, sind keine Synchronisierungsmaßnahmen erforderlich. Wie wiederholbarer Code und Thread-lokaler Speicher.

(1) Wiedereintrittscode

Wenn das Rückgabeergebnis einer Methode vorhersehbar ist und dasselbe Ergebnis zurückgeben kann, solange dieselben Daten eingegeben werden, dann erfüllt sie die Wiedereintrittsanforderungen.

(2) Thread-Lokalspeicher

Wenn die in einem Codeabschnitt erforderlichen Daten mit anderen Codes geteilt werden müssen und die Codes, die Daten teilen, auf diese Weise im selben Thread ausgeführt werden , wir können Der sichtbare Bereich gemeinsam genutzter Daten ist auf einen Thread beschränkt, sodass keine Synchronisierung erforderlich ist, um sicherzustellen, dass es keine Datenkonfliktprobleme zwischen Threads gibt.

Sperroptimierung

Adaptive Drehung

Aufgrund des Blockierens oder Aufweckens eines JAVA-Threads Das Betriebssystem muss den CPU-Status auf „Abgeschlossen“ umschalten, und dieser Statusübergang erfordert Prozessorzeit. Wenn der Inhalt im Synchronisationscodeblock zu einfach ist, dauert der Zustandsübergang wahrscheinlich länger als die Ausführungszeit des Benutzercodes.

Um dieses Problem zu lösen, können wir den Thread, der die Sperre anfordert, später bitten, „einen Moment zu warten“, eine Besetztschleife auszuführen und sich zu drehen. Zu diesem Zeitpunkt wird keine Prozessorausführungszeit verschenkt. Wenn der Spin die begrenzte Anzahl von Malen überschreitet und die Sperre immer noch nicht erfolgreich erhalten werden kann, wird die herkömmliche Methode zum Anhalten des Threads verwendet.

Was ist also adaptiver Spin?

Es handelt sich um dasselbe Sperrobjekt. Wenn die Spin-Wartezeit die Sperre gerade erfolgreich erhalten hat, geht die virtuelle Maschine davon aus, dass die Wahrscheinlichkeit, die Sperre zu erhalten, dieses Mal sehr hoch ist, und lässt sie zu Das Warten dauert relativ länger. Wenn es umgekehrt beim Schleudern nur selten gelingt, einen Overlock zu erreichen, kann der Schleudervorgang weggelassen werden.

Sperrenbeseitigung

bezieht sich auf die gerade in Betrieb genommene virtuelle Maschine. Zeit-Compiler zur Laufzeit, Eliminieren Sie Sperren, die in einigen Codes eine Synchronisierung erfordern, aber als unwahrscheinlich erkannt werden, dass sie einen gemeinsamen Datenwettbewerb haben.

Aufrauen sperren

Wenn eine Reihe kontinuierlicher Vorgänge dasselbe Objekt wiederholt sperren und entsperren oder sogar der Sperrvorgang im Schleifenkörper auftritt, führen häufige gegenseitige Ausschluss-Synchronisierungsvorgänge auch dann zu unnötigen Leistungsverlusten, selbst wenn keine Thread-Konkurrenz vorliegt .
Wenn die virtuelle Maschine erkennt, dass eine Reihe fragmentierter Vorgänge alle dasselbe Objekt sperren, wird der Umfang der Sperrsynchronisierung auf die Außenseite der gesamten Vorgangssequenz vergröbert, sodass sie nur einmal gesperrt werden muss.

Leichte Sperre

Ohne Multithread-Konkurrenz wird die Leistung reduziert. Herkömmliche schwere Sperren führen zu Leistungseinbußen Strafen durch Betriebssystem-Mutexe.
Anwendbare Szenarien: Kein tatsächlicher Wettbewerb, mehrere Threads verwenden abwechselnd Sperren.

Bias-Sperre

Bias-Sperre wird verwendet, um die Verwendung von „No Contention“ zu reduzieren Nur ein Thread Bei Sperren wird der Leistungsverbrauch durch die Verwendung leichter Sperren verursacht. Eine Lightweight-Sperre erfordert jedes Mal, wenn sie eine Sperre beantragt und freigibt, mindestens einen CAS, während eine voreingenommene Sperre während der Initialisierung nur einen CAS erfordert.
Anwendbares Szenario: Es gibt keinen tatsächlichen Wettbewerb und nur der erste Thread, der die Sperre beantragt, wird die Sperre in Zukunft verwenden.

Das Obige ist eine detaillierte Einführung in die effiziente Parallelität in virtuellen JAVA-Maschinen. Weitere verwandte Fragen finden Sie auf der chinesischen PHP-Website: JAVA-Video-Tutorial

Das obige ist der detaillierte Inhalt vonDetaillierte Einführung in die JAVA Virtual Machine (JVM) (8) – effiziente Parallelität. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:csdn.net. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen