Das Ziel der Thread -Kommunikation ist es, Threads zu ermöglichen, Signale aneinander zu senden. Andererseits ermöglicht die Thread -Kommunikation Threads, auf Signale aus anderen Threads zu warten.
Kommunikation über gemeinsame Objekte
Eine einfache Möglichkeit, Signale zwischen Threads zu senden, besteht darin, den Signalwert in den Variablen des gemeinsam genutzten Objekts einzustellen. Thread A setzt die boolesche Mitgliedsvariable HasDatatoprocess in einem Synchronisationsblock zu true, und Thread B liest auch die HasDatatatoprocess -Elementvariable im Synchronisationsblock. In diesem einfachen Beispiel wird ein Objekt verwendet, das ein Signal hält, und stellt die Satz- und Überprüfungsmethoden bereit:
public class mySignal {geschützter boolean hasDatatoprocess = false; public synchronisierte boolean hasDatatoprocess () {return this.hasdatatoprocess; } public synchronisierte void sethasdatatoprocess (boolean HasData) {this.hasdatatoprocess = HasData; }}Die Themen A und B müssen einen Verweis auf eine mySignal -gemeinsame Instanz für die Kommunikation erhalten. Wenn die Referenzen, die sie halten, auf verschiedene MySingal -Instanzen hinweisen, kann einander die Signale des anderen nicht erkennen. Die zu verarbeitenden Daten können in einem gemeinsam genutzten Cache -Bereich gespeichert werden, der getrennt von der MySignal -Instanz gespeichert wird.
Beschäftigt warten
Thread B, der sich auf die Verarbeitung der Daten vorbereitet, wartet darauf, dass die Daten verfügbar sind. Mit anderen Worten, es wartet auf ein Signal von Thread A, das dazu führt, dass HasDatatoprocess () wahr zurückgibt. Thread B läuft in einer Schleife, um auf dieses Signal zu warten:
Protected MySignal SharedSignal = ...... while (!
Wait (), benachrichtigen () und notifyAll ()
Beschäftigtes Warten nutzt die CPU nicht effektiv, die den wartenden Thread ausführt, es sei denn, die durchschnittliche Wartezeit ist sehr kurz. Andernfalls ist es klüger, den wartenden Fadenschlaf oder das Nicht-Laufen zu machen, bis es das Signal erhält, auf das er wartet.
Java verfügt über einen eingebauten Wartenmechanismus, damit Fäden nicht laufend werden können, während er auf Signale wartet. Die Klasse Java.lang.Object definiert drei Methoden, Wait (), Notify () und NotifyAll (), um diesen Wartenmechanismus zu implementieren.
Sobald ein Thread die Wait () -Methode eines Objekts aufruft, wird er zu einem nicht laufenden Status, bis ein anderer Thread die Benachrichtigung () -Methode desselben Objekts aufruft. Um Wait () oder benachrichtigen () anzurufen, muss der Thread zuerst die Sperre dieses Objekts erhalten. Das heißt, der Thread muss Wait () oder benachrichtigen () im Synchronisationsblock aufrufen. Das Folgende ist eine modifizierte Version von MySingal - Mywaitnotify mit Wait () und Notify ():
public class monitorObject {} öffentliche Klasse mywaitnotify {meitorObject myMonitorObject = new MonitorObject (); public void dowait () {synchronized (MyMonitorObject) {try {MyMonitorObject.wait (); } catch (InterruptedException e) {...}}} public void donotify () {synchronized (MyMonitorObject) {MyMonitorObject.notify (); }}}Der wartende Thread ruft Dowait () an, während der Weck-Thread Donotify () aufruft. Wenn ein Thread die Benachrichtigung () -Methode eines Objekts aufruft, wird einer der Threads, die auf das Objekt warten, erweckt und ausführen gelassen (Hinweis: Dieser zu erweichende Thread ist zufällig und kann nicht angegeben werden, welcher Thread aufwachen soll). Ebenfalls angegeben ist eine mellifyall () -Methode, um alle Threads aufzuwecken, die auf ein bestimmtes Objekt warten.
Wie Sie sehen können, egal, ob es sich um den Wartefaden oder der Weck-Thread handelt, rufen Sie im Synchronisationsblock auf () und benachrichtigen (). Das ist obligatorisch! Wenn ein Thread die Objektsperrung nicht enthält, kann er Wait (), benachrichtigen () oder notifyAll () nicht aufrufen. Andernfalls wird eine illegaleMonitorStateException -Ausnahme ausgelöst.
(Hinweis: So wird JVM implementiert. Wenn Sie Warten anrufen, wird zunächst überprüft, ob der aktuelle Thread der Besitzer der Sperre ist, und wirft IllegalMonitorStateExcept aus.)
Aber wie ist das möglich? Wenn der Thread im Synchronisationsblock ausgeführt wird, hält er nicht immer die Sperre des Monitorobjekts (MyMonitor -Objekt)? Kann der wartende Faden blockieren, dass der Weckfaden in den synchronen Block von Donotify () eingeht? Die Antwort lautet: Es ist wahr. Sobald der Thread die Wait () -Methode aufgerufen hat, wird die Sperre für das gehaltene Monitorobjekt veröffentlicht. Dadurch können andere Themen auch Wait () oder benachrichtigen () aufrufen.
Sobald ein Thread aufgeweckt ist, kann der Wait () -Methodenaufruf nicht sofort beendet werden, bis Benachrichtigung () aufgerufen wird.
public class mywaitnotify2 {MonitorObject myMonitorObject = new MonitorObject (); boolean hassignaled = false; public void dowait () {synchronized (MyMonitorObject) {if (! WasSsignaled) {try {MyMonitorObject.wait (); } catch (InterruptedException e) {...}} // Signal löschen und weiter laufen. WASSIGNALED = falsch; }} public void Donotify () {synchronisiert (MyMonitorObject) {WASSIGNALED = TRUE; MyMonitorObject.notify (); }}}
Der Thread verlässt seinen eigenen Synchronisationsblock. Mit anderen Worten, der Waken -Thread muss die Sperre des Monitorobjekts wiedererlangen, bevor er den Wait () -Methodenaufruf beenden kann, da der Aufruf der Warteverfahren im Synchronisationsblock ausgeführt wird. Wenn mehrere Threads durch benachrichtigen () geweckt werden, kann gleichzeitig nur ein Thread die Wait () -Methode verlassen, da jeder Thread die Sperre des Monitorobjekts erhalten muss, bevor Sie Wait () beenden.
Verpasste Signale
Die Methoden "benachrichtigen () und notifyAll () speichern die Methode, die sie aufruft, nicht, denn wenn diese beiden Methoden aufgerufen werden, ist es möglich, dass sich kein Thread im Wartezustand befindet. Das Benachrichtigungssignal wurde verworfen. Wenn ein Thread -Aufruf von Benachrichtigung () vor dem Aufrufen vor dem Aufrufen von Wait () benachrichtigt wird, verpasst der wartende Thread dieses Signal. Dies kann ein Problem sein oder nicht. In einigen Fällen kann der wartende Faden jedoch immer warten lassen und nicht mehr aufwachen, da der Faden das Wecksignal verfehlt.
Um zu vermeiden, dass Signale verloren gehen, müssen sie in der Signalklasse gespeichert werden. Im Beispiel für Mywaitnotify sollte das Benachrichtigungssignal in einer Mitgliedsvariable der Mywaitnotify -Instanz gespeichert werden. Hier ist eine modifizierte Version von Mywaitnotify:
public class mywaitnotify2 {MonitorObject myMonitorObject = new MonitorObject (); boolean hassignaled = false; public void dowait () {synchronized (MyMonitorObject) {if (! WasSsignaled) {try {MyMonitorObject.wait (); } catch (InterruptedException e) {...}} // Signal löschen und weiter laufen. WASSIGNALED = falsch; }} public void Donotify () {synchronisiert (MyMonitorObject) {WASSIGNALED = TRUE; MyMonitorObject.notify (); }}}Beachten Sie, dass die donotify () -Methode die vorgewiesene Variable vor dem Aufrufen von Notify () auf true festlegt. Beachten Sie gleichzeitig, dass die Dowait () -Methode die WaSignal -Variable überprüft, bevor Sie Wait () aufrufen. Wenn während des Zeitraums zwischen dem vorherigen Dowait () -Anruf und diesem Aufruf von Dowait () kein Signal empfangen wird, ruft es nur auf Wait () auf.
(Beweis Hinweis: Um einen Signalverlust zu vermeiden, verwenden Sie eine Variable, um zu speichern, ob sie benachrichtigt wurde. Bevor Sie die Benachrichtigung benachrichtigen, stellen Sie sich fest, dass Sie benachrichtigt wurden. Nach dem Warten stellen Sie fest, dass Sie nicht benachrichtigt wurden und auf die Benachrichtigung warten müssen.)
Falsches Aufwachen
Aus irgendeinem Grund ist es möglich, dass der Thread aufwacht, ohne Benachrichtigung () und notifyAll () aufzurufen. Dies nennt man falsche Aufwachen. Wach ohne Grund auf.
Wenn in der Dowait () -Methode von mywaitnotify2 ein falsches Aufwachen auftritt, kann der wartende Thread nachfolgende Vorgänge ausführen, selbst wenn er nicht das richtige Signal empfängt. Dies kann ernsthafte Probleme mit Ihrer Bewerbung verursachen.
Um falsches Aufwachen zu verhindern, werden die Mitgliedsvariablen, die das Signal enthalten, in einer Weile Schleife und nicht im IF -Ausdruck. Eine Weile wird die Schleife als Spin -Lock bezeichnet (Hinweis: Dieser Ansatz sollte vorsichtig sein. Der aktuelle JVM -Implementierungspin verbraucht die CPU. Wenn die Donotify -Methode lange nicht aufgerufen wird, wird die Dowait -Methode kontinuierlich und die CPU wird zu viel konsumiert). Der erwachte Faden dreht sich, bis der Zustand in der Spinschloss (während Schleife) falsch wird. Die folgende geänderte Version von mywaitnotify2 zeigt dies:
public class mywaitnotify3 {MonitorObject myMonitorObject = new monitorObject (); boolean hassignaled = false; public void dowait () {synchronized (MyMonitorObject) {while (! WasSsignaled) {try {MyMonitorObject.wait (); } catch (InterruptedException e) {...}} // Signal löschen und weiter laufen. WASSIGNALED = falsch; }} public void Donotify () {synchronisiert (MyMonitorObject) {WASSIGNALED = TRUE; MyMonitorObject.notify (); }}}Beachten Sie, dass sich die Wait () -Methode in der WHY -Schleife befindet, nicht im iF -Ausdruck. Wenn der wartende Thread aufwacht, ohne das Signal zu empfangen, wird die aufpasser Variable falsch, und die Schleife wird erneut ausgeführt, was den Weck-Thread dazu veranlasst, in den Wartezustand zurückzukehren.
Mehrere Threads warten auf dasselbe Signal
Wenn Sie mehrere Themen warten und von benachrichtigen () geweckt werden, dürfen jedoch nur einer die Ausführung fortsetzen, es ist auch ein guter Weg, eine Weile -Schleife zu verwenden. Jedes Mal kann nur ein Thread die Monitor -Objektsperrung abrufen, was bedeutet, dass nur ein Thread den Wait () -Anruf beenden und das fassignale Flag (auf False eingestellt) löschen kann. Sobald dieser Thread den Dowait () -Synchronisationsblock verlässt, beenden andere Threads den Wait () -Anruf und überprüfen Sie den Wiessignal -Variablenwert in der while -Schleife. Diese Flagge wurde jedoch durch den ersten erwachten Faden gelöscht, sodass der Rest der erwachten Threads in den Wartezustand zurückkehrt, bis das nächste Mal das Signal ankommt.
Rufen Sie Wait () in Stringkonstanten oder globalen Objekten nicht an
(Beweis Hinweis: Die in diesem Kapitel erwähnte Zeichenfolgekonstante bezieht sich auf Variablen mit konstanten Werten)
Eine frühere Version dieses Artikels verwendet String Constants ("") als Rohrobjekt im Beispiel für Mywaitnotify. Hier ist das Beispiel:
öffentliche Klasse mywaitnotify {String myMonitorObject = ""; boolean hassignaled = false; public void dowait () {synchronized (MyMonitorObject) {while (! WasSsignaled) {try {MyMonitorObject.wait (); } catch (InterruptedException e) {...}} // Signal löschen und weiter laufen. WASSIGNALED = falsch; }} public void Donotify () {synchronisiert (MyMonitorObject) {WASSIGNALED = TRUE; MyMonitorObject.notify (); }}}Das Problem, das durch Aufrufen von Wait () und Notify () im Synchronisationsblock einer leeren Zeichenfolge als Sperre (oder anderer konstanter Zeichenfolge) verursacht wird, besteht darin, dass der JVM/Compiler die konstante Zeichenfolge in dasselbe Objekt umwandelt. Dies bedeutet, dass sie, selbst wenn Sie 2 verschiedene Mywaitnotify -Instanzen haben, alle auf dieselbe leere Zeichenfolge -Instanz beziehen. Dies bedeutet auch, dass das Risiko besteht, dass der Thread Dowait () in der ersten Mywaitnotify -Instanz durch den Thread geweckt wird, der Donotify () in der zweiten Mywaitnotify -Instanz aufruft. Diese Situation kann wie folgt gezeichnet werden:
Zuerst ist dies möglicherweise kein großes Problem. Wenn donotify () in der zweiten Mywaitnotify -Instanz aufgerufen wird, ist das, was wirklich passiert, dass Threads A und B fälschlicherweise erweckt werden. Der Weck-Thread (a oder b) überprüft den Signalwert in der WHOP-Schleife und kehrt dann zum Wartezustand zurück, da Donotify () nicht in der ersten Mywaitnotify-Instanz aufgerufen wird, und dies ist die Instanz, auf die er wartet. Diese Situation entspricht dem Auslösen eines falschen Erwachens. Thread A oder B wacht auf, ohne dass der Signalwert aktualisiert wird. Der Code kümmert sich jedoch um diese Situation, sodass der Thread in den Wartezustand zurückkehrt. Denken Sie daran, dass die Signale in Dowait () und Donotify () durch 2 Mywaitnotify -Instanzen gespeichert werden, auch wenn 4 Threads Call Wait () und Notify () in derselben gemeinsam genutzten String -Instanz aufrufen. Ein Donotify () -Anruf auf mywaitnotify1 kann den Thread von Mywaitnotify2 aufwachen, aber der Signalwert wird nur in MyWaitnotify1 gespeichert.
Das Problem ist, dass donotify () nur Anrufe benachrichtigen () anstelle von notifyAll (), auch wenn 4 Threads auf derselben Zeichenfolge (leerer Zeichenfolge) warten, wird nur ein Thread erweckt. Wenn also Faden A oder B durch ein an C oder D gesendeter Signal geweckt wird, überprüft er seinen eigenen Signalwert, um festzustellen, ob ein Signal empfangen wird, und kehrt dann zum Wartezustand zurück. Weder C noch D wurden geweckt, um den tatsächlich erhaltenen Signalwert zu überprüfen, sodass das Signal verloren ging. Diese Situation entspricht dem Problem der oben genannten fehlenden Signale. C und D wurden an das Signal gesendet, aber auch nicht auf das Signal reagieren.
Wenn die Donotify () -Methodenaufrufe notifyAll () anstelle von notify () () werden, werden alle wartenden Threads geweckt und der Signalwert wird nacheinander überprüft. Die Themen A und B kehren zum Wartezustand zurück, aber nur ein Thread in C oder D bemerkt das Signal und verlässt den Methodenaufruf von Dowait (). Der andere in C oder D kehrt in den Wartezustand zurück, da der Gewinde, der das Signal erhalten hat, den Signalwert (auf False festgelegt) während des Verlassens von Dowait () löscht.
Nach dem Lesen des obigen Absatzes können Sie versuchen, notifyAll () anstelle von benachrichtigen () zu verwenden, aber dies ist eine leistungsbasierte schlechte Idee. Wenn nur ein Thread auf das Signal reagieren kann, gibt es keinen Grund, alle Threads jedes Mal aufzuwachen.
Also: Verwenden Sie im Mechanismus wait ()/notify () keine globalen Objekte, Stringkonstanten usw. Das entsprechende eindeutige Objekt sollte verwendet werden. Beispielsweise hat jede Instanz von MyWaitnotify3 ein eigenes Monitorobjekt, anstatt Wait ()/Notify () in einer leeren Zeichenfolge aufzurufen.
Die oben genannten sind die Informationen zu Java Multi-Threading und Thread Communication. Wir werden in Zukunft weiterhin relevante Informationen hinzufügen. Vielen Dank für Ihre Unterstützung für diese Seite!