Wenn Ihre Java Foundation schwach ist oder Sie kein gutes Verständnis für Java Multithreading haben, lesen Sie bitte diesen Artikel "Erfahren Sie die Thread -Definition, den Zustand und die Eigenschaften von Java Multithreading".
Die Synchronisation war immer ein schwieriger Punkt für Java-Multi-Threading, und sie wird bei der Entwicklung von Android selten verwendet, aber dies ist kein Grund, warum wir mit der Synchronisation nicht vertraut sind. Ich hoffe, dieser Artikel kann es mehr Menschen ermöglichen, die Java -Synchronisation zu verstehen und anzuwenden.
In Multi-Thread-Anwendungen müssen zwei oder mehr Threads Zugriff auf dieselben Daten teilen. Dies wird normalerweise zu einer Rennbedingung, wenn zwei Threads auf dasselbe Objekt zugreifen und jeder Thread eine Methode aufruft, die das Objekt ändert.
Das einfachste Beispiel für Wettbewerbsbedingungen ist: Zum Beispiel sind Zugkarten sicher, aber es gibt Fenster für den Verkauf von Zugkarten überall, jedes Fenster entspricht einem Thread, und so viele Threads teilen alle Zug -Ticketressource. Und es kann seine Atomizität nicht garantieren. Wenn zwei Threads diese Ressource zu einem Zeitpunkt verwenden, sind die von ihnen herausgenommenen Zugtickets gleich (die Sitznummern sind gleich), was den Passagieren Probleme verursacht. Die Lösung ist, dass wir, wenn ein Thread die Zug -Ticket -Ressource verwenden möchte, ihm ein Schloss geben und nach dem Abschluss der Arbeit die Sperre für einen anderen Thread geben, der diese Ressource verwenden möchte. Auf diese Weise tritt die obige Situation nicht auf.
1. Sperren Sie das Objekt
Das synchronisierte Schlüsselwort bietet automatisch Sperren und zugehörigen Bedingungen. In den meisten Fällen, in denen explizite Schlösser erforderlich sind, ist es sehr bequem, synchronisiert zu verwenden. Wenn wir jedoch die Wiedereintrittsklasse und die bedingten Objekte verstehen, können wir das synchronisierte Schlüsselwort besser verstehen. Reentrantlock wird in Java SE 5.0 eingeführt. Die Struktur des Codeblocks wird wie folgt unter Verwendung von Reentrantlock geschützt:
mLock.lock (); try {...} endlich {mLock.unlock ();}Diese Struktur stellt sicher, dass nur ein Faden jederzeit in den kritischen Bereich eintritt. Sobald ein Faden das Sperrobjekt blockiert, kann kein anderer Faden die Sperranweisung übergeben. Wenn andere Threads aufrufen, werden sie blockiert, bis der erste Thread das Sperrobjekt veröffentlicht. Es ist sehr notwendig, den Entsperrvorgang endlich einzulegen. Wenn eine Ausnahme im kritischen Bereich auftritt, muss das Schloss freigegeben werden, andernfalls werden andere Threads für immer blockiert.
2. Bedingte Objekt <br /> Bei der Eingabe des kritischen Bereichs wird festgestellt, dass es erst nach Erfüllung eines bestimmten Zustands ausgeführt werden kann. Verwenden Sie ein bedingtes Objekt, um Threads zu verwalten, die ein Schloss erhalten haben, aber keine nützliche Arbeit leisten können. Bedingte Objekte werden auch als bedingte Variablen bezeichnet.
Schauen wir uns das folgende Beispiel an, um festzustellen, warum bedingte Objekte benötigt werden
Angenommen, wir müssen in einem Szenario Banktransfers verwenden, wir schreiben zuerst die Bankklasse und sein Konstruktor muss auf die Anzahl der Konten und die Menge der Konten übertragen werden.
öffentliche Klasse Bank {private double [] Konten; Private Lock Banklock; öffentliche Bank (int n, Double InitialBalance) {Accounts = New Double [n]; Banklock = new Reentrantlock (); für (int i = 0; i <conaces.length; i ++) {Accounts [i] = InitialBalance; }}}Als nächstes wollen wir Geld abheben und eine Auszahlungsmethode schreiben. Aus dem Übertragungsantrag nach ist der Empfänger und der Betrag Übertragungsbetrag. Infolgedessen stellten wir fest, dass der Gleichgewicht des Übertragers nicht ausreicht. Wenn andere Threads dem Übertragung genug Geld sparen, kann die Übertragung erfolgreich sein. Dieser Thread hat jedoch das exklusive Sperre erfasst, und andere Threads können die Sperre nicht erwerben, um Einlagenvorgänge durchzuführen. Aus diesem Grund müssen wir bedingte Objekte einführen.
public void Transfer (int von, int to, int betragen) {banklock.lock (); try {while (Konten [von] <stum) {// warte}} endlich {banklelock.unlock (); }}Ein Sperrobjekt verfügt über mehrere verwandte Bedingungsobjekte. Sie können die Newcondition -Methode verwenden, um ein Bedingungsobjekt zu erhalten. Nachdem wir das Bedingungsobjekt erhalten haben, nennen wir die wartende Methode, und der aktuelle Thread ist blockiert und das Schloss wird aufgegeben.
öffentliche Klasse Bank {private double [] Konten; Private Lock Banklock; Privatzustand; öffentliche Bank (int n, Double InitialBalance) {Accounts = New Double [n]; Banklock = new Reentrantlock (); // Die Bedingungsobjekt -Bedingung = Banklock.newcondition () erhalten; für (int i = 0; i <conaces.length; i ++) {Accounts [i] = InitialBalance; }} public void Transfer (int von, int, int, intmengen) löst InterruptedException aus {banklolock.lock (); Versuchen Sie {while (Konten [von] <Betrag) {// Blockieren Sie den aktuellen Thread und geben Sie die Sperrbedingung auf. }} endlich {banklelock.unlock (); }}} Der Thread, der auf das Schloss wartet, unterscheidet sich im Wesentlichen von dem Thread, der die wartende Methode aufruft. Sobald ein Thread die wartende Methode aufgerufen hat, wird der Wartensatz dieser Bedingung eingegeben. Wenn das Schloss verfügbar ist, kann der Thread nicht sofort entsperren, sondern befindet sich in einem Blockierungszustand, bis ein anderer Thread die Signalall -Methode unter derselben Bedingung aufruft. Wenn ein anderer Thread bereit ist, Geld an unseren vorherigen Übertragung zu überweisen, rufen Sie einfach die Bedingung an. Signalall (); Dieser Anruf reaktiviert alle Threads, die auf diesen Zustand warten.
Wenn ein Thread die wartende Methode aufruft, kann er sich nicht selbst reaktivieren und hofft, dass andere Threads die Signalall -Methode aufrufen, um sich selbst zu aktivieren. Wenn keine anderen Fäden den wartenden Faden aktivieren, tritt Deadlock auf. Wenn alle anderen Threads blockiert sind und die letzten aktiven Thread -Aufrufe vor dem Entfernen anderer Threads erwarten, wird es auch blockiert. Kein Thread kann andere Threads entsperren und das Programm wird suspendiert.
Wann wird Signalall aufgerufen? Normalerweise sollte es vorteilhaft sein, Signalall aufzurufen, wenn sich die Richtung des Threads ändert. In diesem Beispiel sollte sich der Warte -Thread, wenn sich ein Kontostand ändert, die Möglichkeit haben, den Restbetrag zu überprüfen.
public void Transfer (int von, int to, int orient) löst unterbrochene Ausnahme {banklelock.lock () aus; Versuchen Sie {while (Konten [von] <Betrag) {// Blockieren Sie den aktuellen Thread und geben Sie die Sperrbedingung auf. } // Übertragungsvorgang ... Condition.Signalall (); } endlich {Banklock.unlock (); }}Wenn die Signalall -Methode aufgerufen wird, wird ein wartender Faden nicht sofort aktiviert. Es entsperren einfach die wartenden Threads, damit diese Threads den Zugriff auf das Objekt erwerben können, indem der aktuelle Thread die synchrone Methode beendet wird. Eine andere Methode ist das Signal, das zufällig einen Faden entsperrt. Wenn der Thread immer noch nicht ausgeführt wird, wird er erneut blockiert. Wenn kein anderer Thread erneut Signal aufruft, wird das System abgestimmt.
3.. Synchronisierte Schlüsselwörter
Die Grenzflächen für Sperr- und Zustandsgrenzwerte bieten den Programmierern ein hohes Maß an Verriegelungskontrolle. In den meisten Fällen ist jedoch keine solche Kontrolle erforderlich, und ein in der Java -Sprache eingebetteter Mechanismus kann verwendet werden. Ab Java Version 1.0 verfügt jedes Objekt in Java über ein internes Schloss. Wenn eine Methode mit dem synchronisierten Schlüsselwort deklariert wird, schützt die Sperre des Objekts die gesamte Methode. Um diese Methode aufzurufen, muss der Thread die interne Objektschloss erhalten.
Mit anderen Worten,
public synchronisierte void -Methode () {}Äquivalent zu
public void method () {this.lock.lock (); try {} endlich {this.lock.unlock ();} Im obigen Bankbeispiel können wir die Überweisungsmethode der Bankklasse als synchronisiert anstatt eine angezeigte Sperre zu verwenden.
Es gibt nur einen verwandten Zustand für die interne Objektschloss. Wartenvergrößerung wird zu einem Thread zum Wartensatz hinzugefügt. Benachrichtigen oder benachrichtigen Sie die Methoden, die den wartenden Thread entsperren. Mit anderen Worten, Wartezeit entspricht der Aufrufbedingung.
Unsere obige Beispielübertragungsmethode kann auch so geschrieben werden:
public synchronisierte void -Übertragung (int von, int, int, intmenge) löst InterruptedException aus (while (Accounts [von] <pension) {wait (); } // Operation übertragen ... notifyAll (); }Sie können sehen, dass die Verwendung des synchronisierten Schlüsselworts zum Schreiben von Code viel einfacher ist. Um diesen Code zu verstehen, müssen Sie natürlich verstehen, dass jedes Objekt über eine interne Schloss verfügt und dass das Schloss eine interne Bedingung hat. Die Sperre verwaltet die Threads, die versuchen, die synchronisierte Methode einzugeben, und die Bedingungen verwaltet die Threads, die Warten aufrufen.
4. Synchronous Blocking <BR /> Oben sagten wir, dass jedes Java -Objekt über ein Schloss verfügt und ein Thread die Synchronisationsmethode aufrufen kann, um das Schloss zu erhalten, und es gibt einen anderen Mechanismus, um das Schloss zu erhalten. Durch Eingeben eines Synchronisationsblocks, wenn der Faden in die folgende Form der Blockierung eingeht:
synchronisiert (obj) {}Also bekam er das Schloss des OBJ. Werfen wir einen Blick auf die Bankklasse
öffentliche Klassenbank {private double [] Konten; private Object lock = new Object (); öffentliche Bank (int n, Double InitialBalance) {Accounts = New Double [n]; für (int i = 0; i <conaces.length; i ++) {Accounts [i] = InitialBalance; }} public void Transfer (int von, int to, intmengen) {synchronisiert (Sperre) {// Übertragungsvorgang ...}}}Hier wird die Erstellung von Sperrobjekten einfach verwendet, um die von jedem Java -Objekt gehaltenen Schlösser zu verwenden. Manchmal verwenden Entwickler die Sperre eines Objekts, um zusätzliche Atomvorgänge zu implementieren, die als Client -Sperren bezeichnet werden. Zum Beispiel sind die Vektorklasse, ihre Methoden synchron. Nehmen Sie nun an, dass der Bankguthaben in Vector gespeichert ist
public void Transfer (Vector <Double> Konten, int von, int to, int tenbetrag) {contaces.set (von, contacts.get (von) -Amount); conces.set (to, contacts.get (an)+Betrag;}Die Get and Set -Methoden der Vecror -Klasse sind synchron, aber dies hat uns nicht geholfen. Nachdem der erste Anruf abgeschlossen ist, ist es durchaus möglich, dass einem Thread das Recht auf Übertragungsmethode verweigert wird, so
public void Transfer (Vektor <double> Konten, int von, int to, int orient) {synchronized (Konten) {concesS.set (von, contacts.get (von) -Amount); contaces.set (to, contacts.get (an)+Betrag;}}Die Client -Sperre (Synchroncodeblöcke) ist sehr zerbrechlich und wird normalerweise nicht empfohlen. Im Allgemeinen ist es am besten, die Klassen zu verwenden, die unter dem Paket java.util.concurrent bereitgestellt werden, z. B. die Blockierung von Warteschlangen. Wenn die Synchronisationsmethode für Ihr Programm geeignet ist, versuchen Sie, die Synchronisierungsmethode zu verwenden. Es kann die Anzahl der geschriebenen Code verringern und die Wahrscheinlichkeit von Fehlern verringern. Wenn Sie die einzigartigen Funktionen verwenden müssen, die durch die Struktur der Sperre/Bedingung bereitgestellt werden, verwenden Sie nur Sperre/Bedingung.
Das Obige dreht sich alles um diesen Artikel, ich hoffe, es wird für das Lernen aller hilfreich sein.