Synchronisierter Schlüsselwort
Synchronisiert werden wir Schlösser aufrufen, werden hauptsächlich zum Sperrieren von Methoden und Codeblöcken verwendet. Wenn eine Methode oder ein Codeblock synchronisiert ist, wird der Code höchstens ein Thread gleichzeitig ausgeführt. Wenn mehrere Threads auf die Sperrenmethode/den gleichen Objekt zugreifen, wird gleichzeitig nur ein Thread ausgeführt, und die verbleibenden Threads müssen darauf warten, dass der aktuelle Thread das Codessegment vor der Ausführung ausführt. Die verbleibenden Threads können jedoch auf die nicht verlockenden Codeblöcke im Objekt zugreifen.
Synchronisierte enthält hauptsächlich zwei Methoden: synchronisierte Methode und synchronisierter Block.
Synchronisierte Methode
Deklarieren Sie die synchronisierte Methode, indem Sie das synchronisierte Schlüsselwort zur Methodeerklärung hinzufügen. wie:
öffentliche synchronisierte Leere getResult ();
Die synchronisierte Methode steuert den Zugriff auf Klassenmitgliedvariablen. Wie vermeidet es die Zugriffskontrolle von Klassenmitgliedvariablen? Wir wissen, dass die Methode das synchronisierte Schlüsselwort verwendet, um anzuzeigen, dass die Methode gesperrt ist. Wenn ein Thread auf die modifizierte Methode zugreift, müssen Sie feststellen, ob andere Threads "exklusiv" sind. Jede Klasseninstanz entspricht einem Schloss. Jede synchronisierte Methode muss die Sperre der Klasseninstanz der Methode aufrufen, bevor sie ausgeführt werden kann. Ansonsten ist der Faden, zu dem er gehört, blockiert. Sobald die Methode ausgeführt wurde, ist das Schloss exklusiv. Das Schloss wird erst freigegeben, wenn es aus der Methode zurückkehrt und der blockierte Faden das Schloss erhalten kann.
Tatsächlich hat die synchronisierte Methode Fehler. Wenn wir eine große Methode als synchronisiert deklarieren, wirkt sich dies stark auf die Effizienz aus. Wenn mehrere Threads auf eine synchronisierte Methode zugreifen, wird die Methode gleichzeitig ausgestellt, während andere Threads warten müssen. Wenn die Methode jedoch nicht synchronisiert wird, können alle Threads gleichzeitig ausgeführt werden, wodurch die Gesamtausführungszeit verkürzt wird. Wenn wir also wissen, dass eine Methode nicht von mehreren Threads ausgeführt wird oder es kein Problem der Ressourcenfreigabe gibt, müssen wir das synchronisierte Schlüsselwort nicht verwenden. Wenn wir jedoch das synchronisierte Schlüsselwort verwenden müssen, können wir die synchronisierte Methode durch synchronisierten Codeblock ersetzen.
synchronisierter Block
Der synchronisierte Codeblock spielt die gleiche Rolle wie die synchronisierte Methode, außer dass er den kritischen Bereich so kurz wie möglich macht. Mit anderen Worten, es schützt nur die benötigten gemeinsam genutzten Daten, sodass dieser Vorgang für die verbleibenden langen Codeblöcke geblieben ist. Die Syntax ist wie folgt:
Synchronisiert (Objekt) {// Code, mit dem Zugriffskontrolle} Wenn wir das synchronisierte Schlüsselwort auf diese Weise verwenden müssen, müssen wir eine Objektreferenz als Parameter verwenden. Normalerweise verwenden wir diesen Parameter häufig als diesen.Es gibt das folgende Verständnis von synchronisiertem (dies):
1. Wenn zwei gleichzeitige Threads auf diesen synchronisierten (this) synchronisierten Codeblock in demselben Objektobjekt zugreifen, kann nur ein Thread innerhalb eines Zeitpunkts ausgeführt werden. Ein weiterer Thread muss warten, bis der aktuelle Thread diesen Codeblock ausführt, bevor er den Codeblock ausführen kann.
2. Wenn jedoch ein Thread auf einen synchronisierten (this) -Synchronisationscode-Codeblock eines Objekts zugreift, kann ein anderer Thread weiterhin auf den nicht synchronisierten (this) Synchronisationscodeblock im Objekt zugreifen.
3.. Es ist besonders wichtig, dass bei einem Thread auf einen synchronisierten (this) -Synchronisationscodeblock eines Objekts zugreift, andere Threads aus dem Zugriff auf alle anderen synchronisierten Synchronisationscodeblöcke im Objekt blockiert werden.
4. Das dritte Beispiel gilt auch für andere Synchronisationscodeblöcke. Das heißt, wenn ein Thread auf einen synchronisierten (this) -Synchronisationscode -Codeblock eines Objekts zugreift, wird die Objektsperrung dieses Objekts erhalten. Infolgedessen werden andere Threads auf alle synchronen Code -Teile des Objektobjekts zugreifen.
Sperren
In Java Multithreading gibt es ein Prinzip "zuerst, komm später", das heißt, wer auch immer den Schlüssel greift, wird ihn zuerst verwenden. Wir wissen, dass Java, um Probleme mit dem Ressourcenwettbewerb zu vermeiden, Synchronisationsmechanismen verwendet, um zu vermeiden, und die Synchronisationsmechanismen werden unter Verwendung des Konzepts der Schlösser kontrolliert. Wie werden sich in Java -Programmen Schlösser widerspiegeln? Hier müssen wir zwei Konzepte herausfinden:
Was ist ein Schloss? Im täglichen Leben handelt es sich um einen Versiegeler, der zu Türen, Kisten, Schubladen und anderen Objekten hinzugefügt wird, die andere daran hindern, zu schauen oder zu stehlen, und spielt eine schützende Rolle. Gleiches gilt für Java. Schlösser spielen eine Rolle beim Schutz von Objekten. Wenn ein Thread ausschließlich eine bestimmte Ressource einnimmt, möchten Sie nicht andere Threads verwenden, sondern sie verwenden? Reden wir darüber, wenn ich fertig bin!
In einem Java -Programm, in dem die JVM ausgeführt wird, muss die JVM die von zwei Arten von Threads gemeinsam genutzten Daten koordinieren:
1. Instanzvariablen, die im Haufen gespeichert sind
2. Klassenvariablen im Methodenbereich gespeichert.
In einer java -virtuellen Maschine ist jedes Objekt und jede Klasse einem Monitor logisch zugeordnet. Für ein Objekt schützt der zugehörige Monitor die Instanzvariablen des Objekts. Für Klassen schützen Monitore Klassenvariablen. Wenn ein Objekt keine Instanzvariablen hat oder eine Klasse keine Variablen hat, überwacht der zugehörige Monitor nichts.
Um die exklusive Überwachungsfähigkeit des Monitors zu implementieren, verbindet die Java Virtual Machine eine Sperre für jedes Objekt und jede Klasse. Repräsentiert die Privilegien, dass nur ein Thread jederzeit haben kann. Themen müssen beim Zugriff auf Instanzvariablen oder Klassenvariablen nicht sperren. Wenn ein Faden ein Schloss erwirbt, ist es für andere Fäden unmöglich, das gleiche Schloss zu erwerben, bevor er das Schloss freigibt. Ein Thread kann dasselbe Objekt mehrmals sperren. Für jedes Objekt hält die java virtuelle Maschine einen Schlossschalter. Jedes Mal, wenn der Faden das Objekt erhält, wird der Zähler um 1 erhöht, und jedes Mal, wenn er freigibt, wird der Zähler um 1 reduziert. Wenn der Zählerwert 0 ist, wird das Schloss vollständig freigesetzt.
Java -Programmierer müssen keine Schlösser selbst hinzufügen, Objektschlösser werden intern von Java Virtual Machines verwendet. In einem Java -Programm müssen Sie nur die synchronisierte Block- oder synchronisierte Methode verwenden, um einen Überwachungsbereich zu markieren. Wenn Sie einen Überwachungsbereich eingeben, sperrt die Java Virtual Machine das Objekt oder die Klasse automatisch.
Ein einfaches Schloss
Bei der Verwendung von Synchronisierungen verwenden wir solche Schlösser:
public class threadtest {public void test () {synchronisiert (this) {// etwas tun}}}Synchronisierte stellt sicher, dass nur ein Thread gleichzeitig Dosen ausführt. Hier ist die Verwendung von Schloss statt synchronisiert:
public class threadtest {lock lock = new Lock (); public void test () {lock.lock (); // etwas lock.unlock (); }}Die Lock () -Methode sperrt das Lock -Instance -Objekt, sodass alle Threads, die die Lock () -Methode auf dem Objekt aufrufen, blockiert werden, bis die entsperr () -Methode des Sperrobjekts aufgerufen wird.
Was ist gesperrt?
Vor dieser Frage müssen wir klar sein: Unabhängig davon, ob das synchronisierte Schlüsselwort der Methode oder dem Objekt hinzugefügt wird, ist die Sperre, die es erwirbt, ein Objekt. In Java kann jedes Objekt als Schloss verwendet werden, das sich hauptsächlich in den folgenden drei Aspekten widerspiegelt:
Für Synchronisationsmethoden ist das Schloss das aktuelle Instanzobjekt.
Für den Synchronisierungsmethodenblock ist das Sperre ein Objekt, das in den synchronisierten Klammern konfiguriert ist.
Für statische Synchronisationsmethoden ist das Schloss das Klassenobjekt des aktuellen Objekts.
Schauen wir uns zunächst das folgende Beispiel an:
public class threadTest_01 implementiert runnable {@Override public synchronisierte void run () {für (int i = 0; i <3; i ++) {System.out.println (Thread.CurrentThread (). getName ()+"run ......"); }} public static void main (String [] args) {für (int i = 0; i <5; i ++) {neuer Thread (neuer ThreadTest_01 (), "Thread_"+i) .start (); }}}Teilläufe Ergebnisse:
Thread_2run ... Thread_2run ... Thread_4run ... Thread_4run ... Thread_3run ... Thread_3run ... Thread_3run ... Thread_3run ... Thread_2run ... Thread_4run ...
Dieses Ergebnis unterscheidet sich etwas von dem erwarteten Ergebnis (diese Threads laufen hier herum). Logischerweise erzeugt die Run -Methode plus das synchronisierte Schlüsselwort einen Synchronisationseffekt. Diese Threads sollten die Auslaufmethode nacheinander ausführen. Wie oben erwähnt, handelt es sich bei der Mitgliedsmethode nach dem Hinzufügen von synchronisierten Schlüsselwörtern zu einer Mitgliedsmethode tatsächlich um eine Sperre. Der spezifische Punkt besteht darin, das Objekt selbst zu verwenden, an dem sich die Mitgliedsmethode als Objektschloss befindet. In diesem Beispiel haben wir jedoch neue 10 Thread -Test -Objekte, und jeder Thread hält die Objektsperrung seines eigenen Thread -Objekts, was definitiv keinen synchronen Effekt erzeugt. Also: Wenn diese Threads synchronisiert werden sollen, sollten die von diesen Threads gehaltenen Objektsperrungen geteilt und einzigartig sein!
Welches Objekt ist zu diesem Zeitpunkt synchronisiert? Was es sperrt, ist, dieses Synchron -Method -Objekt zu nennen. Das heißt, wenn das Thread -Test -Objekt Synchronisationsmethoden in verschiedenen Threads ausführt, bildet es sich gegenseitig ausschließend. Den Effekt der Synchronisation erreichen. Ändern Sie also den obigen neuen Thread (neuer ThreadTest_01 (), "Thread_" + i) .Start (); zu einem neuen Thread (Threadtest, "Thread_" + i) .Start ();
Für Synchronisationsmethoden ist das Schloss das aktuelle Instanzobjekt.
Das obige Beispiel verwendet die synchronisierte Methode. Schauen wir uns den synchronisierten Codeblock an:
öffentliche Klasse ThreadTest_02 erweitert Thread {private String -Sperre; privater Zeichenfolge Name; public threadTest_02 (String -Name, String -Sperre) {this.name = name; this.lock = lock; } @Override public void run () {synchronized (lock) {for (int i = 0; i <3; i ++) {System.out.println (Name+"run ......"); }}} public static void main (String [] args) {String lock = new String ("test"); für (int i = 0; i <5; i ++) {new threadTest_02 ("threadTest_"+i, lock) .Start (); }}}Auslaufergebnisse:
ThreadTest_0 run...ThreadTest_0 run...ThreadTest_0 run...ThreadTest_1 run...ThreadTest_1 run...ThreadTest_1 run...ThreadTest_1 run...ThreadTest_4 run...ThreadTest_4 run...ThreadTest_4 run...ThreadTest_3 run...ThreadTest_3 run...ThreadTest_3 run...ThreadTest_2 run...ThreadTest_2 run...ThreadTest_2 run...
In der Hauptmethode erstellen wir eine String -Objekt -Sperre und weisen diesem Objekt jedem ThreadTest2 -Thread -Objekt -private Variable -Sperre zu. Wir wissen, dass es in Java einen Stringpool gibt, sodass die privaten Variablen dieser Threads tatsächlich auf denselben Bereich im Heap -Speicher hinweisen, dh auf den Bereich, in dem die Sperrvariablen in der Hauptfunktion gespeichert sind, sodass die Objektschloss eindeutig und geteilt ist. Fadensynchronisation! !
Das Sperr -String -Objekt, das hier synchronisiert wurde.
Für den Synchronisierungsmethodenblock ist das Sperre ein Objekt, das in den synchronisierten Klammern konfiguriert ist.
public class threadTest_03 erweitert Thread {public synchronisierte statische void test () {für (int i = 0; i <3; i ++) {System.out.println (Thread.currentThread (). getName ()+"run ......"); }} @Override public void run () {Test (); } public static void main (String [] args) {für (int i = 0; i <5; i ++) {new threadTest_03 (). start (); }}}Auslaufergebnisse:
Thread-0 Run ... Thread-0 Run ... Thread-0 Run ... Thread-4 Run ... Thread-4 Run ... Thread-4 Run ... Thread-1 Run ... Thread-1 Run ... Thread-1 Run ... Thread-2 Run ... Thread-2 Run ... Thread-2 Run ... Thread-3 Run ... Thread-3-Thread-3-Thread-3-Auslauf ...
In diesem Beispiel verwendet die Run -Methode eine Synchronisationsmethode und eine statische Synchronisationsmethode. Was ist das synchronisierte Schloss hier? Wir wissen, dass Static über das Objekt hinausgeht und auf Klassenebene liegt. Daher ist eine Objektschloss die Klasseninstanz der Klasse, in der sich die statische Freisetzung befindet. Da in der JVM alle geladenen Klassen eindeutige Klassenobjekte haben, das einzige ThreadTest_03.Class -Objekt in dieser Instanz. Egal wie viele Fälle der Klasse, die wir erstellen, seine Klasseninstanz ist immer noch eins! Das Objektschloss ist also einzigartig und geteilt. Fadensynchronisation! !
Für statische Synchronisationsmethoden ist das Schloss das Klassenobjekt des aktuellen Objekts.
Wenn eine Klasse eine synchronisierte statische Funktion A und eine synchronisierte Instanzfunktion B definiert, stellt das gleiche Objekt OBJ dieser Klasse keine Synchronisation dar, wenn er in mehreren Threads auf zwei Methoden A und B zugreift, da ihre Schlösser unterschiedlich sind. Die Verriegelung der Methode A ist das Objekt OBJ, während das Schloss von B die Klasse ist, zu der OBJ gehört.
Schloss Upgrade
In Java gibt es vier Staaten: Lock-Free-Bundesstaat, voreingenommener Schlosszustand, Leichtgewichtszustand und Schwergewichts-Lock-Status, der allmählich mit dem Wettbewerb eskalieren wird. Das Schloss kann verbessert werden, kann jedoch nicht herabgestuft werden, was bedeutet, dass das vorgespannte Schloss nach dem Upgrade auf ein leichtes Schloss nicht auf das voreingenommene Schloss herabgestuft werden kann. Diese Strategie des Lock -Upgrade, kann jedoch nicht herabgestuft werden, um die Effizienz des Erhaltens und Freisetzung von Schlösser zu verbessern. Der Hauptteil unten ist eine Zusammenfassung des Blogs: Parallelität (ii) in Java SE1.6 synchronisiert.
Sperrspin
Wir wissen, dass, wenn ein Thread eine Synchronisationsmethode/einen Codeblock eingibt, wenn er feststellt, dass die Synchronisationsmethode/der Codeblock von anderen besetzt ist, wartet und gibt einen Blockierungszustand ein. Die Leistung dieses Prozesses ist gering.
Bei der Begegnung der Konkurrenz des Schlosses oder des Wartens auf Dinge kann der Thread weniger bestrebt sein, in den Blockierungszustand einzusteigen, aber warten Sie, ob das Schloss sofort veröffentlicht wird. Dies ist der Schlossspin. Bis zu einem gewissen Grad kann der Sperrspin den Faden optimieren.
Positive Schloss
Positive Schlösser werden hauptsächlich verwendet, um das Leistungsproblem von Schlössern ohne Konkurrenz zu lösen. In den meisten Fällen haben Sperrschlösser nicht nur keine Mehrfach-Thread-Konkurrenz, sondern werden auch immer mehrmals vom gleichen Thread erhalten. Um den Gewinde die Schlösser zu geringeren Kosten zu erwerben, werden voreingenommene Schlösser eingeführt. Wenn ein Faden eine Sperre erhält, kann der Faden das Objekt mehrmals sperren. Jedes Mal, wenn ein solcher Vorgang durchgeführt wird, wird ein Overhead-Verbrauch aufgrund der CAS-Operation (CPU-Vergleichs- und Swap-Operation). Um diesen Overhead zu reduzieren, ist das Schloss tendenziell der erste Faden, der ihn erhält. Wenn das Schloss während des nächsten Ausführungsprozesses nicht von anderen Threads erfasst wird, muss der Thread, der das vorgespannte Schloss hält, nie wieder synchronisiert werden.
Wenn andere Fäden versuchen, um das voreingenommene Schloss zu konkurrieren, setzt der Gewinde, das das vorgespannte Schloss hält, das Schloss frei.
Ausdehnung der Sperrung
Mehrere oder mehrere Aufrufe zu Schlössern mit zu kleiner Granularität sind nicht so effizient wie das Aufrufen von Schlössern mit einer großen Granularitätsschloss.
Leichtes Schloss
Die Grundlage für das leichte Schloss zur Verbesserung der Synchronisationsleistung des Programms besteht darin, dass "für die meisten Schlösser keine Konkurrenz während des gesamten Synchronisationszyklus vorliegt", was empirische Daten sind. Ein leichtes Schloss erstellt einen Speicherplatz namens Lock -Rekord im Stapelrahmen des aktuellen Fadens, mit dem das aktuelle Zeigen und der Status des Sperrobjekts gespeichert werden. Wenn es keine Konkurrenz gibt, nutzt das Lightweight Lock den CAS -Betrieb, um den Overhead der Verwendung von Mutexes zu vermeiden. Wenn jedoch die Konkurrenz von Schloss vorliegt, erfolgt zusätzlich der CAS -Betrieb zusätzlich zu dem Overhead von Mutexes. Bei Wettbewerb wird das leichte Schloss langsamer als das herkömmliche Schwergewichtsschloss.
Die Fairness des Schlosses
Das Gegenteil von Fairness ist Hunger. Was ist "Hunger"? Wenn ein Thread keine CPU -Laufzeit erhalten kann, da andere Threads immer die CPU besetzen, nennen wir den Thread "Hungry zum Tod". Die Lösung für den Hunger heißt "Fairness" - alle Threads können CPU -Laufmöglichkeiten fair machen.
Es gibt mehrere Hauptgründe für den Fadenhunger:
Hochprioritätsfäden verbrauchen die CPU-Zeit aller Threads mit niedriger Priorität. Wir können seine Priorität für jeden Thread von 1 bis 10 einzeln festlegen. Je höher der Prioritätsthread ist, desto mehr Zeit benötigt es, um die CPU zu erhalten. Für die meisten Anwendungen ist es am besten, seinen Prioritätswert nicht zu ändern.
Der Thread ist dauerhaft in einem Zustand blockiert, der darauf wartet, den Synchronisationsblock einzugeben. Der Synchroncodebereich von Java ist ein wichtiger Faktor, der den Fadenhunger verursacht. Die synchronen Codeblöcke von Java garantieren nicht die Reihenfolge der in sie eingeben Threads. Dies bedeutet, dass es theoretisch ein oder mehrere Threads gibt, die immer blockiert werden, wenn Sie versuchen, den Synchron -Code -Bereich einzugeben, da andere Threads ihnen immer überlegen sind, Zugang zu erhalten, was dazu führt, dass die CPU -Laufmöglichkeit und nicht "verhungert".
Der Thread wartet auf ein Objekt, das selbst auch dauerhaft auf die Fertigstellung wartet. Wenn sich mehrere Threads auf der Ausführung der Wait () -Methode befinden und aufgerufene () aufzurufen () nicht garantiert, dass ein Thread erweckt wird, kann ein Thread in einem Zustand des kontinuierlichen Wartens befinden. Daher besteht das Risiko, dass ein wartender Faden niemals geweckt wird, da andere wartende Fäden immer geweckt werden können.
Um das Problem des "Hungers" von Threads zu lösen, können wir Schlösser verwenden, um Fairness zu erreichen.
Wiedereinzugbarkeit von Schlössern
Wir wissen, dass der Thread, wenn ein Thread ein Objekt anfordert, das eine Sperre von einem anderen Thread hält, der Thread blockiert wird. Kann es jedoch erfolgreich sein, wenn der Thread ein Objekt anfordert, das ein Sperre von selbst enthält? Die Antwort ist, dass Erfolg erfolgreich sein kann und die Erfolgsgarantie der "Wiedereintritt" von Thread -Schlössern ist.
"Wiederermer" bedeutet, dass Sie Ihr eigenes internes Schloss wieder bekommen können, ohne zu blockieren. wie folgt:
public class father {public synchronisierte void method () {// etwas tun}} Public Class Child erweitert Vater {public synchronisierte void method () {// etwas super.method (); }}
Wenn es nicht wieder eingetreten ist, wird der obige Code abgestimmt, da die Methode des Kindes () zuerst die eingebaute Schloss des Vater der Elternklasse erfasst und dann das eingebaute Schloss des Kindes erfasst. Wenn Sie die Methode der Elternklasse aufrufen, müssen Sie wieder zum integrierten Schloss der übergeordneten Klasse zurückkehren. Wenn es nicht wieder eingetreten ist, können Sie in eine Sackgasse fallen.
Die Implementierung der Wiedereinzugbarkeit von Java multithreading besteht darin, eine Anforderungsberechnung und einen Thread zu verknüpfen, der sie durch jedes Schloss einnimmt. Wenn die Zählung 0 ist, wird angenommen, dass das Schloss nicht besetzt ist und jeder Thread den Besitz des Schlosses erhalten kann. Wenn ein Thread erfolgreich anfordert, zeichnet der JVM den Thread auf, der die Sperre hält und die Anzahl auf 1. Setze auf 1. Wenn andere Threads die Sperre anfordern, müssen sie warten. Wenn der Thread anfordert, die Sperre erneut zu erhalten, beträgt die Anzahl +1; Wenn der besetzende Thread den Synchroncode -Block verlässt, beträgt die Anzahl -1, bis er 0 ist, die Sperre veröffentlicht. Nur dann können andere Threads die Möglichkeit haben, sich an der Schloss zu besitzen.
Lock und seine Implementierungsklasse
java.util.concurrent.locks bietet einen sehr flexiblen Verriegelungsmechanismus und bietet eine Schnittstelle und Klasse eines Frameworks für Verriegelung und Wartezeit. Es unterscheidet sich von eingebauter Synchronisation und Monitoren, was eine stärkere Flexibilität bei der Verwendung von Verriegelung und Bedingungen ermöglicht. Sein Klassenstrukturdiagramm lautet wie folgt:
Reentrantlock: Eine Wiedereintritts -Mutex -Sperre, die Hauptimplementierung der Sperrgrenzfläche.
ReentranTreadWriteLock:
ReadWriteLock: ReadWriteLock unterhält ein Paar verwandte Sperren, eine für schreibgeschützte Operationen und die andere für Schreibvorgänge.
Semaphor: Ein Zählsemaphor.
Bedingung: Der Zweck des Schlosses besteht darin, dass der Faden das Schloss erwerben und prüft, ob ein bestimmtes Erhalten erfüllt ist.
CyclicBarrier: Eine synchrone Hilfsklasse, mit der eine Gruppe von Fäden aufeinander warten kann, bis sie einen gemeinsamen Barrierpunkt erreicht.