Java5 enthält bereits Lesen und Schreiben von Sperren in das Paket von Java.util.Concurrent. Trotzdem sollten wir die Prinzipien hinter seiner Umsetzung verstehen.
Java -Implementierung von Lese-/Schreibschloss
Geben wir zunächst einen Überblick über die Bedingungen für das Lesen und das Schreiben von Zugriff auf Ressourcen:
Wenn Sie keinen Thread lesen, wird der Schreibvorgang durchgeführt und kein Thread die Schreibvor Operation anfordert.
Kein Thread lesen und schreiben Operationen.
Wenn ein Thread eine Ressource lesen möchte, solange kein Thread in die Ressource schreibt und kein Thread anfordert, in die Ressource zu schreiben. Wir gehen davon aus, dass Anfragen nach Schreibvorgängen wichtiger sind als Anfragen nach Lesevorgängen, daher müssen wir die Priorität von Schreibanfragen erhöhen. Wenn häufig Lesevorgänge auftreten und wir die Priorität von Schreibvorgängen nicht erhöhen, wird "Hunger" nicht auftreten. Der Thread, der den Schreibvorgang anfordert, wird blockiert, bis alle Lesefäden von ReadWriteLock entsperrt sind. Wenn die Read -Operation -Berechtigungen des neuen Threads immer garantiert sind, wird der Thread, der auf den Schreibvorgang wartet, weiter blockiert, und das Ergebnis ist "Hunger". Daher kann der Lesevorgang nur garantiert fortgesetzt werden, wenn kein Thread für Schreibvorgänge einröstet, und kein Thread fordert, dass die Sperre für Schreibvorgänge bereit ist.
Wenn andere Threads keine Operationen in der freigegebenen Ressource lesen oder schreiben, kann ein Thread eine Schreibschloss für die freigegebene Ressource erhalten und dann Operationen auf die freigegebene Ressource schreiben. Es spielt keine Rolle, wie viele Threads -Anforderungen Schreibschlösser und in welcher Reihenfolge, es sei denn, Sie möchten die Fairness der Schreibschlossanforderung sicherstellen.
Gemäß der obigen Beschreibung wird einfach eine Lese-/Schreibschloss implementiert und der Code ist wie folgt
öffentliche Klasse ReadWriteLock {private int readers = 0; private int writers = 0; private int writeRequests = 0; public synchronisierte void lockRead () löst unterbrochene Ausnahme {while (writers> 0 || writeRequests> 0) {wait (); } leser ++; } public synchronisierte void UnlockRead () {Leser--; notifyAll (); } public synchronisierte void lockWrite () löst InterruptedException aus {WriteRequests ++; while (readers> 0 || writers> 0) {Wait (); } WriteRequests--; Schriftsteller ++; } public synchronisierte void UnlockWrite () löst InterruptedException aus. notifyAll (); }}Lesen Sie in der ReadWriteLock -Klasse Lock und schreiben Sie die Lock -Lock jeweils über eine Methode zum Erwerb und zur Veröffentlichung des Schlosses.
Die Implementierung von Read Lock ist in lockread (). Solange kein Thread ein Schreibschloss hat (Autoren == 0) und kein Thread eine Schreibschloss (WriteRequests == 0) fordert, können alle Threads, die eine Lesesperrung erhalten möchten, erfolgreich erhalten werden.
Die Implementierung von Write Lock ist in LockWrite (). Wenn ein Thread eine Schreibschloss erhalten möchte, fügt er zuerst 1 zur Schreibschlossanforderungsnummer (WriteRequests ++) hinzu und bestimmen dann, ob er wirklich ein Schreibschloss erhalten kann. Wenn kein Thread eine Lesesperrung (Leser == 0) enthält und kein Thread ein Schreibschloss (Autoren == 0) enthält, können Sie ein Schreibschloss erhalten. Es spielt keine Rolle, wie viele Themen das Schreiben von Schlössern beantragen.
Es ist zu beachten, dass die Benachrichtigungsmethode bei beiden UnlockRead, UnlockWrite anstelle von Benachrichtigung aufgerufen wird. Um diesen Grund zu erklären, können wir uns die folgende Situation vorstellen:
Wenn ein Thread darauf wartet, das Leseschloss zu erwerben und ein Thread darauf wartet, das Schreibschloss zu erwerben. Wenn einer der Threads, die auf die Lesesperrung warten, von der Benachrichtigungsmethode geweckt wird, aber da immer noch ein Thread die Schreibschloss anfordert (WriteRequests> 0), wird der erwachte Thread erneut in den Blockierungszustand eingetragen. Keiner der Threads, die auf das Schreibschloss warten, wurde jedoch geweckt, als wäre nichts passiert (Anmerkung des Übersetzers: Signalverlust). Wenn Sie die Notifyall -Methode verwenden, werden alle Threads geweckt und bestimmen dann, ob sie die von ihnen angeforderte Sperre erhalten können.
Die Verwendung von Notifyall hat auch einen Vorteil. Wenn mehrere Lesefäden auf die Lesesperrung warten und kein Thread auf die Schreibschloss wartet, können nach dem Aufrufen von UnlockWrite () alle Threads, die auf das Leseschloss warten, das Leseschloss sofort erwerben - anstelle von jeweils nur eine.
Wiedereintritt des Lese-/Schreibschloss
Die oben implementierte Lese-/Schreibschloss ist nicht wieder eingetragen und wird blockiert, wenn ein Thread, der das Schreibschloss bereits hält, die Schreibschloss erneut anfordert. Der Grund ist, dass es bereits einen Schreib Thread gibt - es ist selbst. Betrachten Sie auch das folgende Beispiel:
Um ReadWriteLock wieder einzubeziehen, müssen einige Verbesserungen vorgenommen werden. Das Folgende wird den Wiedereintritt von Read Lock bzw. den Wiedereintritt von Schreibschloss verarbeiten.
Lesen Sie das Lock -Wiedereintritt
Um das Read -Sperre von ReadWriteLock -Wiedereintritt zu machen, müssen wir zunächst Regeln für das Read -Lock -Wiedereintritt festlegen:
Um sicherzustellen, dass die Lesesperrung in einem Thread wieder eingetreten ist, erfüllen Sie entweder die Bedingungen für die Erzielung der Lesesperrung (keine Schreib- oder Schreibanforderung) oder halten Sie die Lesesperrung bereits (unabhängig davon, ob eine Schreibanforderung vorliegt oder nicht). Um festzustellen, ob ein Thread bereits eine Lesesperrung enthält, kann eine Karte verwendet werden, um den Thread zu speichern, der bereits eine Lesesperrung enthält, und wie oft der entsprechende Thread eine Lesesperrung erwerbt. Wenn festgestellt werden muss, ob ein Thread eine Lesesperrung erhalten kann, wird die in der Karte gespeicherten Daten verwendet, um ein Urteil zu fällen. Das Folgende ist der geänderte Code der Methoden Lockread und UnlockRead:
öffentliche Klasse ReadWriteLock {private map <Thread, Integer> ReadingThreads = New HashMap <Thread, Integer> (); private int writers = 0; private int writeRequests = 0; public synchronisierte void lockread () löst unterbrochene Ausnahme {Thread CallingThread = Thread.currentThread (); while (! cangranTreadAccess (CallingThread)) {Wait (); } ReadingThreads.put (CallingThread, (getAccesscount (CallingThread) + 1)); } public synchronisierte void UnlockRead () {Thread CallingThread = Thread.currentThread (); int accessCount = getAccessCount (CallingThread); if (AccessCount == 1) {ReadingThreads.remove (CallingThread); } else {ReadingThreads.put (CallingThread, (AccessCount -1)); } notifyAll (); } private boolean cangranTreadAccess (Thread Callthread) {if (writers> 0) return false; if (isReader (Calling thread) return true; if (writeRequests> 0) return false; return true;} private int getReadAccessCount (Thread Callthread) {Integer accessCount = ReadingThreads.get (CallingThread); ReadingThreads.get (CallingThread)! = NULL;Im Code können wir sehen, dass der Wiedereintritt von Lesesperrs nur dann zulässig ist, wenn kein Thread eine Schreibschloss hat. Darüber hinaus haben Wiedereintrittslesschlösser eine höhere Priorität als Schreibschlösser.
Schreiben Sie Lock -Wiedereintritt
Schreiben Sie das Schreibrückgang nur dann zu, wenn ein Thread bereits eine Schreibschloss enthält (die Schreibschloss wiedererlangen). Das Folgende ist der geänderte Code des Methodenschloss und des EntsperrWrite.
öffentliche Klasse ReadWriteLock {private map <Thread, Integer> ReadingThreads = New HashMap <Thread, Integer> (); private int writeAccessses = 0; private int writeRequests = 0; privates Thread WritingThread = NULL; public synchronisierte void lockWrite () löst unterbrochene Ausnahme {WriteRequests ++ aus; Thread CallingThread = Thread.currentThread (); while (! cangrantWriteAccess (CallingThread)) {Wait (); } WriteRequests--; WriteAccesses ++; WritingThread = Callthread; } public synchronisierte void UnlockWrite () löst InterruptedException aus. if (writeAccessses == 0) {writingThread = null; } notifyAll (); } private boolean cangrantWriteAccess (Thread CallingThread) {if (hasReaders ()) return false; if (writingThread == null) return true; if (! IsWriter (Calling thread)) Return Falsch; zurückkehren; } private boolean hasReaders () {return Readthreads.size ()> 0; } private boolean isWriter (Thread CallingThread) {return writingThread == CallingThread; }}Achten Sie darauf, wie Sie damit umgehen können, wenn er feststellt, ob der aktuelle Thread das Schreibschloss erwerben kann.
Lesen Sie das Lock -Upgrade, um Sperre zu schreiben
Manchmal möchten wir einen Thread, der ein Leseschloss hat, um ein Schreibschloss zu erhalten. Um solche Vorgänge zuzulassen, muss dieser Thread der einzige Thread mit einer Lesesperrung sein. Writelock () muss einige Änderungen vornehmen, um dieses Ziel zu erreichen:
öffentliche Klasse ReadWriteLock {private map <Thread, Integer> ReadingThreads = New HashMap <Thread, Integer> (); private int writeAccessses = 0; private int writeRequests = 0; privates Thread WritingThread = NULL; public synchronisierte void lockWrite () löst unterbrochene Ausnahme {WriteRequests ++ aus; Thread CallingThread = Thread.currentThread (); while (! cangrantWriteAccess (CallingThread)) {Wait (); } WriteRequests--; WriteAccesses ++; WritingThread = Callthread; } public synchronisierte void UnlockWrite () löst InterruptedException aus. if (writeAccessses == 0) {writingThread = null; } notifyAll (); } private boolean cangrantWriteAccess (Thread CallingThread) {if (isollyReader (CallingThread)) Return True; if (hasReaders ()) false zurückgeben; if (writingThread == null) return true; if (! IsWriter (Calling thread)) Return Falsch; zurückkehren; } private boolean hasReaders () {return Readthreads.size ()> 0; } private boolean isWriter (Thread CallingThread) {return writingThread == CallingThread; } private boolean IsollyReader (Thread Thread) {return readers == 1 && ReadingThreads.get (CallingThread)! = null; }}Jetzt kann die ReadWriteLock -Klasse von einem Leseschloss auf ein Schreibschloss aktualisiert werden.
Schreiben Sie Lock Down, um Lock zu lesen
Manchmal möchten Themen, die Schreibschlösser haben, auch Leseschlösser erhalten. Wenn ein Thread eine Schreibschloss hat, kann natürlich andere Threads keine Lesesperre oder ein Schreibschloss haben. Daher besteht keine Gefahr für einen Thread, der ein Schreibschloss hat und dann ein Leseschloss erhält. Wir müssen nur eine einfache Änderung der oben genannten CangranTeadaccess -Methode vornehmen:
öffentliche Klasse ReadWriteLock {private boolean cangranTreadAccess (Thread CallingThread) {if (iswriter (CallingThread)) Return True; if (writingThread! = null) return false; if (isReader (CallingThread) Return True; if (writeRequests> 0) false; return true;}}Vollständige Implementierung von wiedereintretender ReadWriteLock
Unten finden Sie die vollständige ReadWriteLock -Implementierung. Um das Lesen und das Verständnis des Code zu erleichtern, wurde der obige Code einfach neu gestaltet. Der refaktorierte Code ist wie folgt.
öffentliche Klasse ReadWriteLock {private map <Thread, Integer> ReadingThreads = New HashMap <Thread, Integer> (); private int writeAccessses = 0; private int writeRequests = 0; privates Thread WritingThread = NULL; public synchronisierte void lockread () löst unterbrochene Ausnahme {Thread CallingThread = Thread.currentThread (); while (! cangranTreadAccess (CallingThread)) {Wait (); } ReadingThreads.put (CallingThread, (GetReadAccessCount (CallingThread) + 1)); } private boolean cangranTreadAccess (Thread Callthread) {if (isWriter (CallingThread)) Return True; if (haswriter ()) return false; if (isReader (Calling thread)) true zurück; if (haswriterequests ()) false zurückgeben; zurückkehren; } public synchronisierte void UnlockRead () {Thread CallingThread = Thread.currentThread (); if (! isReader (CallingThread)) {neue IllegalMonitorStateException werfen ("Thread nicht" + "Halten Sie eine Lesesperrung auf diesem ReadWriteLock"); } int accessCount = getReadAccessCount (CallingThread); if (AccessCount == 1) {ReadingThreads.remove (CallingThread); } else {ReadingThreads.put (CallingThread, (AccessCount -1)); } notifyAll (); } public synchronisierte void lockWrite () löst InterruptedException aus {WriteRequests ++; Thread CallingThread = Thread.currentThread (); while (! cangrantWriteAccess (CallingThread)) {Wait (); } WriteRequests--; WriteAccesses ++; WritingThread = Callthread; } public synchronisierte void UnlockWrite () löst unterbrochene Ausnahme {if (! isWriter (thread.currentThread ()) {Neue IllegalMonitorStateException ("Calling Thread" + "Halten Sie das Schreibschloss auf dieses ReadWriteLock");} WriteAcess--; boolean CangrantWriteAccess (Thread Thread) {if (isollyreader (Calling thread)) RETTER; if (accessCount == null) return accessCount.intValue (); && ReadingThreads.get (CallingThread)! = NULL; } private boolean haswriter () {return writingThread! = null; } private boolean isWriter (Thread CallingThread) {return writingThread == CallingThread; } private boolean haswriterequests () {return this.writerequests> 0; }}Rufen Sie Unlock () in schließlich an
Bei Verwendung von ReadWriteLock zum Schutz kritischer Zonen ist es wichtig, Readunlock () und WriteUnlock () im endgültigen Block aufzurufen. Dies geschieht, um sicherzustellen, dass ReadWriteLock erfolgreich entsperrt werden kann und andere Threads das Schloss anfordern können. Hier ist ein Beispiel:
lock.lockwrite (); try {// kritische Abschnittscode durchführen, die eine Ausnahme ausführen kann} endlich {lock.unlockwrite ();}Die obige Codestruktur kann sicherstellen, dass ReadWriteLock auch veröffentlicht wird, wenn eine Ausnahme in den kritischen Bereich ausgelöst wird. Wenn die UnlockWrite -Methode nicht im endgültigen Block aufgerufen wird und eine Ausnahme in den kritischen Abschnitt ausgelöst wird, bleibt ReadWriteLock im Schreibschlossstatus, wodurch alle Threads das Aufrufen von LockRead () oder LockWrite () zum Blockieren führen. Der einzige Faktor, der ReadWriteLock erneut entspannen kann, kann sein, dass ReadWriteLock wieder eingetreten ist. Wenn eine Ausnahme ausgelöst wird, kann der Thread das Schloss erfolgreich erwerben, dann den kritischen Abschnitt ausführen und UnlockWrite () erneut aufrufen, wodurch ReadWriteLock erneut veröffentlicht wird. Aber was ist, wenn der Faden das Schloss nicht mehr erwirbt? Das Aufrufen von UnlockWrite ist daher sehr wichtig, um robusten Code zu schreiben.
Das obige ist die Zusammenstellung von Java-Multi-Thread-Informationen. Wir werden in Zukunft weiterhin relevante Informationen hinzufügen. Vielen Dank für Ihre Unterstützung für diese Website!