Dieser Artikel folgt hauptsächlich zwei früheren Artikeln mit Multi-Threading, um die Sicherheit von Thread-Sicherheit in Java Multi-Threading zusammenzufassen.
1. Ein typisches Beispiel für Java -Fadensicherheit
public class threadtest {public static void main (String [] args) {Account = neues Konto ("123456", 1000); DrawMoneyRunnable DrawMoneyRunnable = New DrawMoneyRunnable (Account, 700); Thread myThread1 = neuer Thread (DrawMoneyRunnable); Thread myThread2 = neuer Thread (DrawMoneyRunnable); myThread1.Start (); MyThread2.Start (); }} Klasse DrawMoneyRunnable Geräte Runnable {private Account; privates doppelter Drawing; public drawMoneyRunnable (Kontokonto, doppelter Drawamount) {Super (); this.account = Account; this.drawamount = Drawamount; } public void run () {if (account.getBalance ()> = DrawAmount) {// 1 system.out.println ("Die Auszahlung war erfolgreich, die Auszahlung des Geldes lautet:" + drawAmount); Double Balance = Account.getBalance () - Drawamount; Account.SetBalance (Saldo); System.out.println ("Balance ist:" + Balance); }}} Klassenkonto {private String accountNo; privates Doppelbilanz; public Account () {} public account (String accountNo, Doppelbilanz) {this.accountno = accountNo; this.balance = balance; } public String getAccountNo () {return accountNo; } public void setAccountno (String accountNo) {this.accountno = accountNo; } public double getBalance () {Return Balance; } public void setBalance (Doppelbilanz) {this.balance = balance; }}Das obige Beispiel ist leicht zu verstehen. Es gibt eine Bankkarte mit einem Guthaben von 1.000. Das Programm simuliert die Szene, in der Sie und Ihre Frau gleichzeitig Geld am Geldautomaten abheben. Führen Sie dieses Programm mehrmals aus und haben möglicherweise Ausgabeergebnisse in mehreren verschiedenen Kombinationen. Einer der möglichen Ausgänge ist:
1 Die Auszahlung des Geldes ist erfolgreich, die Auszahlung des Geldes beträgt: 700,0
2 Gleichgewicht ist: 300,0
3 Die Auszahlung des Geldes ist erfolgreich, die Auszahlung des Geldes beträgt: 700,0
4 Der Restbetrag ist: -400.0
Mit anderen Worten, für eine Bankkarte mit einem Restbetrag von nur 1.000 können Sie insgesamt 1.400 abheben, was offensichtlich ein Problem darstellt.
Nach der Analyse liegt das Problem in der Unsicherheit der Ausführung in einer Java-Umgebung mit mehreren Threaden. Die CPU kann zufällig zwischen mehreren Threads im Bereitschaftszustand wechseln, sodass es sehr wahrscheinlich ist, dass die folgende Situation auftritt: Wenn Thread1 den Code bei // 1 ausführt, ist die Beurteilungsbedingung wahr. Zu diesem Zeitpunkt wechselt die CPU zu Thread2, führt den Code AT // 1 aus und stellt fest, dass er immer noch wahr ist. Dann wird Thread2 ausgeführt, dann zu Thread1 umgestellt und dann die Ausführung abgeschlossen. Zu diesem Zeitpunkt erscheinen die obigen Ergebnisse.
Wenn es um Sicherheitsprobleme von Thread geht, bedeutet dies tatsächlich, dass der Zugriff auf gemeinsame Ressourcen in einer Umgebung mit mehreren Threaden zu Inkonsistenz in dieser gemeinsamen Ressource führen kann. Um die Sicherheit von Thread-Sicherheit zu vermeiden, sollte der gleichzeitige Zugriff auf diese gemeinsame Ressource in einer Umgebung mit mehreren Threads vermieden werden.
2. Synchronisationsmethode
Die synchronisierte Keyword -Modifikation wird der Methodendefinition für den Zugriff auf gemeinsame Ressourcen hinzugefügt, wodurch diese Methode als Synchronisierungsmethode bezeichnet wird. Es kann einfach verstanden werden, dass diese Methode gesperrt ist und sein gesperrtes Objekt das Objekt selbst ist, an dem sich die aktuelle Methode befindet. In einer Umgebung mit mehreren Threads müssen Sie bei der Ausführung dieser Methode zuerst diese Synchronisationsschloss erhalten (und höchstens nur ein Thread kann sie erhalten). Nur wenn der Thread diese Synchronisationsmethode ausführt, wird das Sperrobjekt freigegeben und andere Threads können diese Synchronisationsschloss usw. erhalten ...
Im obigen Beispiel ist die freigegebene Ressource ein Kontoobjekt, und bei Verwendung der Synchronisierungsmethode kann sie Probleme mit der Thread -Sicherheit lösen. Fügen Sie einfach das synsronisierte Schlüsselwort vor der Run () -Methode hinzu.
public synchronisierte void run () {// ....}3.. Codeblöcke synchronisieren
Wie oben analysiert, erfordert die Lösung von Sicherheitsproblemen von Thread nur die Begrenzung der Unsicherheit des Zugangs zu gemeinsamen Ressourcen. Bei der Verwendung der Synchronisationsmethode wird der gesamte Methodenkörper zu einem synchronen Ausführungszustand, der dazu führen kann, dass der Synchronisationsbereich auftritt. Daher kann eine andere Synchronisationsmethode - der Synchronisationscode -Block - direkt für den Code gelöst werden, der Synchronisation benötigt.
Das Format des synchronen Codeblocks lautet:
synchronisiert (obj) {// ...}Unter ihnen ist OBJ das Schlossobjekt, daher ist es wichtig zu wählen, welches Objekt gesperrt werden soll. Im Allgemeinen wird dieses gemeinsame Ressourcenobjekt als Sperrobjekt ausgewählt.
Wie im obigen Beispiel ist es am besten, das Kontoobjekt als Sperrobjekt zu verwenden. (Natürlich ist es auch möglich, dies auszuwählen, da der Erstellungs -Thread die Runnable -Methode verwendet. Wenn es sich um einen Thread handelt, der direkt die Thread -Methode erstellt, spielt die Verwendung dieses Objekts als Synchronisationsschloss tatsächlich keine Rolle, da es sich
4. Synchronisationsschloss der Objektschloss
Wie wir oben sehen können, gibt es eine einfache Lösung, genau weil wir so vorsichtig mit der Auswahl synchroner Sperrobjekte vorsichtig sein müssen? Es kann die Entkopplung synchroner Sperrobjekte aus gemeinsamen Ressourcen erleichtern und gleichzeitig die Sicherheitsprobleme von Threads gut lösen.
Durch die Verwendung von Sperrobjekt -Synchronisationssperrungen kann dieses Problem problemlos gelöst werden. Das einzige, was zu beachten ist, ist, dass das Schlossobjekt eine Eins-zu-Eins-Beziehung zum Ressourcenobjekt haben muss. Das allgemeine Format der Synchronisationsschloss für Sperre Objekte lautet:
Klasse X {// Zeigen Sie das Objekt an, das das Sperre-Synchronisationsschloss definiert, das eine Eins-zu-Eins-Beziehung zur Shared Resource Private Final Lock = New Reentrantlock () hat; public void m () {// lock lock.lock (); // ... Code, der Thread-Safe-Synchronisation erfordert // Lock Lock Lock.unlock () loslassen; }}5.wait ()/notify ()/notifyall () Thread -Kommunikation
Diese drei Methoden werden im Blog -Beitrag "Java Summary Series: Java.lang.Object" erwähnt. Obwohl diese drei Methoden hauptsächlich beim Multithreading verwendet werden, sind sie tatsächlich lokale Methoden in der Objektklasse. Daher kann theoretisch jedes Objektobjekt als Hauptton dieser drei Methoden verwendet werden. Bei der tatsächlichen Multi-Threading-Programmierung können nur durch Synchronisieren des Sperrobjekts diese drei Methoden die Kommunikation zwischen mehreren Threads abgeschlossen werden.
Warten Sie (): Lassen Sie den aktuellen Thread warten und es in einen Wartesportstatus eingeben. Bis ein anderer Thread die Methode benachrichtigen () oder notifyAll () des synchronen Sperrobjekts aufweckt, um den Thread aufzuwecken.
Notify (): Weck einen einzelnen Thread auf dieses synchrone Sperrobjekt. Wenn mehrere Threads auf dieses Synchronen-Sperrobjekt warten, wird einer der Threads für den Weckbetrieb ausgewählt. Nur wenn der aktuelle Thread die Sperre am synchronen Sperrobjekt aufgibt, kann der erwachte Thread ausgeführt werden.
NotifyAll (): Weck alle Threads auf, die auf dieses synchrone Sperrobjekt warten. Nur wenn der aktuelle Thread die Sperre am synchronen Sperrobjekt aufgibt, kann der erwachte Thread ausgeführt werden.
paket com.qqyumidi; public class threadtest {public static void main (String [] args) {Account Account = neues Konto ("123456", 0); Thread DrawMoneyThread = New DrawMoneyThread ("Geld Thread erhalten", Konto, 700); Thread DepositMoneyThread = New DepositMoneyThread ("Geldspeicher", Konto, 700); DrawMoneyThread.Start (); DepositMoneyThread.Start (); }} Klasse DrawMoneyThread erweitert Thread {privates Konto; private doppelte Menge; public DrawMoneyThread (String -ThreadName, Konto, Doppelbetrag) {Super (ThreadName); this.account = Account; this.amount = Menge; } public void run () {für (int i = 0; i <100; i ++) {account.draw (Betrag, i); }}} class DepositemoneyThread erweitert Thread {private Account; private doppelte Menge; public deponMoneyThread (String -ThreadName, Konto, Doppelbetrag) {Super (ThreadName); this.account = Account; this.amount = Menge; } public void run () {für (int i = 0; i <100; i ++) {account.Deposit (Menge, i); }}} Klassenkonto {private String accountNo; privates Doppelbilanz; // Identifizieren Sie, ob bereits im Konto private boolesche Flagge = False eine Anzahlung vorhanden ist. public Account () {} public account (String accountNo, Doppelbilanz) {this.accountno = accountNo; this.balance = balance; } public String getAccountNo () {return accountNo; } public void setAccountno (String accountNo) {this.accountno = accountNo; } public double getBalance () {Return Balance; } public void setBalance (Doppelbilanz) {this.balance = balance; } / ** * Geld sparen * * @param depositAMOUNT * / public synchronisierte void deposit (Double DepositAMOUNT, INT I) {if (flag) {// jemand im Konto hat bereits Geld gespart, und der aktuelle Thread muss kaum erwarten, dass er versuchte. Warten(); // 1 system.out.println (Thread.currentThread (). GetName () + "Wait Operation" + " - i =" + i); } catch (interruptedException e) {e.printstacktrace (); }} else {// Starten Sie das Speichern von System.out.println (thread.currentThread (). Setbalance (Saldo + Einzahlung); Flag = wahr; // andere Threads aufwecken, notifyAll (); // 2 try {thread.sleep (3000); } catch (interruptedException e) {e.printstacktrace (); } System.out.println (Thread.currentThread (). GetName () + "- Geld sparen- Ausführung ist abgeschlossen" + "- i =" + i); }} / ** * Geld abziehen ich); Warten(); System.out.println (Thread.currentThread (). GetName () + "Wait -Operation ausführen" + "Wait Operation" + " - i =" + i); } catch (interruptedException e) {e.printstacktrace (); }} else {// starten Sie Geld system.out.println (thread.currentThread (). Setbalance (GetBalance () - Drawamount); Flag = Falsch; // andere Threads aufwecken, notifyAll (); System.out.println (Thread.currentThread (). GetName () + "-Geld abheben-Ausnahme ist abgeschlossen" + "-i =" + i); // 3}}} Das obige Beispiel demonstriert die Verwendung von Wait ()/notify ()/notifyAll (). Einige Ausgangsergebnisse sind:
Der Geldabhebungs-Thread beginnt, den Wartevorgang auszuführen und den Wartevorgang auszuführen- i = 0
Einzahlung von Threads sparen: 700.0 - i = 0
Sparen Sie Geld-Thread-Save-Geld-Execution-i = 0
Der Geldsparen-Thread muss den Wartevorgang durchführen- i = 1
Der Geldabhebungs-Thread führt den Wartenbetrieb und den Wartevorgang aus- i = 0
Geld abheben Thread Geld abheben Geld: 700.0 - i = 1
Geldauszahlungsthread-mit dem DDRAWAL-Ausmaß-i = 1
Der Thread, um Geld abzuheben
Der Geldsparen-Thread führt den Wartevorgang aus- i = 1
Einzahlung von Threads sparen: 700.0 - i = 2
Sparen Sie Geld-Thread-Save-Geld-Execution-i = 2
Der Auszahlungs-Thread führt den Wartevorgang aus und führt den Wartevorgang aus- i = 2
Geldfaden mit dem Geld abheben Geld: 700,0 - i = 3
Geldauszahlungsthread-mit dem DDRAWAL-Ausmaß-i = 3
Der Thread, um Geld abzuheben
Einzahlung von Threads sparen: 700.0 - i = 3
Sparen Sie Geld-Thread-Save-Geld-Execution-i = 3
Der Geldsparen-Thread muss den Wartevorgang durchführen- i = 4
Der Geldabhebungs-Thread führt den Wartenbetrieb und den Wartevorgang aus- i = 4
Geldfaden mit dem Geld abheben Geld: 700,0 - i = 5
Geldauszahlungsthread-mit dem DDRAWAL-Ausmaß-i = 5
Der Thread, um Geld abzuheben
Der Geldsparen-Thread führt den Wartevorgang aus- i = 4
Einzahlung von Threads sparen: 700.0 - i = 5
Sparen Sie Geld-Thread-Save-Geld-Execution-i = 5
Der Geldsparen-Thread muss den Wartevorgang durchführen- i = 6
Der Geldabhebungs-Thread führt den Wartenbetrieb und den Wartevorgang aus- i = 6
Geldfaden mit dem Geld abheben Geld ausgelegt: 700,0 - i = 7
Geldauszahlungsthread-mit dem DDRAWAL-Ausmaß-i = 7
Der Geldabhebungs-Thread beginnt, den Wartevorgang auszuführen und den Wartevorgang auszuführen- i = 8
Der Geldsparen-Thread führt den Wartevorgang aus- i = 6
Sparen von Thread -Einzahlungen: 700.0 - i = 7
Daher müssen wir auf die folgenden Punkte achten:
1. Nachdem die Wait () -Methode ausgeführt wurde, tritt der aktuelle Thread sofort in den wartenden Blockierstatus ein, und der nachfolgende Code wird nicht ausgeführt.
2. Nachdem die Methode von Notify ()/NotifyAll () ausgeführt wurde, wird das Thread-Objekt (Any-Notify ()/All-Notifyall ()) in diesem Synchronisationssperrobjekt geweckt. Das Synchronisations -Lock -Objekt wird zu diesem Zeitpunkt jedoch nicht freigegeben. Das heißt, wenn hinter notify ()/notifyall () Code vorhanden ist, wird er weiter fortgesetzt. Nur wenn der aktuelle Thread ausgeführt wird, wird das Synchronisations -Sperrobjekt freigegeben.
3. Nach der Ausführung von Notify ()/NotifyAll () wird der aktuelle Thread in einen Blockierungszustand eingetragen, wenn eine Sleep () -Methode vorhanden ist, aber die Synchronisationsobjektschloss wird nicht freigegeben und wird immer noch von selbst erhalten. Dann wird der Thread nach einem bestimmten Zeitraum, den nächsten 2, weiterhin ausgeführt;
4. Wait ()/Notify ()/nitifyAll () vervollständigt die Kommunikation oder Zusammenarbeit zwischen Threads basierend auf verschiedenen Objektschlössern. Wenn es sich also um eine andere Synchronisationsobjektschloss handelt, verliert es seine Bedeutung. Gleichzeitig ist die Synchronisationsobjektschloss am besten eine Eins-zu-Eins-Korrespondenz mit dem gemeinsam genutzten Ressourcenobjekt beibehalten.
5. Wenn der Wait -Thread aufwacht und ausgeführt wird, wird der Wait () -Methodcode, der das letzte Mal ausgeführt wurde, weiterhin ausgeführt.
Natürlich ist das obige Beispiel relativ einfach, nur um einfach die Methode wait ()/notify ()/noiTifyall () zu verwenden, aber im Wesentlichen ist es bereits ein einfaches Modell des Produzenten-Verbrauchers.
Artikelserie:
Erläuterung von Java-Multi-Thread-Instanzen (i)
Detaillierte Erklärung von Java-Multi-Thread-Instanzen (ii)
Detaillierte Erläuterung von Java-Multi-Thread-Instanzen (III)