Was sind die Schlösser in Java?
Ich konnte diese Frage nicht beantworten, nachdem ich <java gleichzeitige Programmierung> gelesen habe, was zeigt, dass ich nicht genug über das Konzept der Schlösser verstehe. Also schaute ich wieder durch den Inhalt des Buches und hatte plötzlich das Gefühl, meine Stirn zu öffnen. Es scheint, dass der beste Weg zum Lernen ist, mit Problemen zu lernen und sie zu lösen.
In Java gibt es zwei Haupttypen von Schlössern: Synchronisierte interne Sperren und Anzeigen von java.util.concurrent.locks.lock. Aber wenn Sie sorgfältig darüber nachdenken, scheint es, dass die Zusammenfassung nicht korrekt ist. Es sollte eine Reihe von Schlössern sein, die von Java-integrierten Schlössern und gleichzeitigen Implementierungen implementiert werden.
Warum heißt das, denn in Java ist alles ein Objekt, und Java hat ein Schloss in jedes Objekt integriert, das auch als Objektschloss/interner Schloss bezeichnet werden kann. Der relevante Sperrvorgang wird durch synchronisiert abgeschlossen.
Aufgrund der Mängel bei der Implementierung von synchronisierten und der Komplexität gleichzeitiger Szenarien hat jemand ein explizites Schloss entwickelt, und diese Schlösser stammen von java.util.concurrent.locks.locks. Natürlich wurde es in JDK1.5 und spätere Versionen eingebaut.
synchronisiert
Schauen wir uns zunächst die synchronisierten Blicks an, die häufiger verwendet werden. Es wird auch in meiner täglichen Arbeit verwendet. Synchronisiert wird verwendet, um einen Sperrmechanismus für einen bestimmten Codeblock bereitzustellen. Es wird implizit eine Sperre in Java -Objekten haben. Dieses Schloss wird als intrinsische oder Monitorschlösser bezeichnet. Der Thread erfasst diese Sperre automatisch, bevor der durch synchronisierte Block geschützt ist, bis die Sperre automatisch freigegeben wird, nachdem der Code abgeschlossen ist (oder auch eine Ausnahme sein kann). Eingebaute Schlösser schließen sich gegenseitig aus. Ein Schloss kann nur von einem Thread gleichzeitig gehalten werden, was auch zu mehreren Fäden führt, und die Fäden hinter dem Schloss werden nach dem Halten blockiert. Dadurch können die Thread -Sicherheit des Codes die Atomizität gewährleisten.
Wieder betreten
Da sich die integrierten Schlösser von Java gegenseitig ausschließen und die nachfolgenden Fäden zu einer Blockierung führen, was passiert, wenn der Gewinde, der das Schloss hält, beim Versuch, das Sperre zu erhalten, erneut eintritt? Zum Beispiel eine der folgenden Situationen:
public class baseclass {public synchronisierte void do () {System.out.println ("is base"); }} öffentliche Klasse Sonclass erweitert BaseClass {public Synchronized void do () {System.out.println ("Is Son"); Super.do (); }} Sonclass Son = New SonClass (); Son.do ();Zu diesem Zeitpunkt hält die DO -Methode der abgeleiteten Klasse zuerst das Schloss und gilt dann erneut in das Schloss und hält es beim Aufrufen von Super.do (). Wenn sich das Schloss gegenseitig ausschließt, sollte es zu diesem Zeitpunkt abgestimmt werden.
Das Ergebnis ist jedoch nicht der Fall, da das interne Schloss das charakteristische RE -Entrant hat, dh das Schloss implementiert einen Wiedereintrittsmechanismus, Referenzzählmanagement. Wenn Thread 1 die Sperre des Objekts A enthält, wird die Referenz zum Sperren A durch Hinzufügen von 1 berechnet. Wenn Thread 1 erneut ein Sperren A erwerbt, hält Thread 1 weiterhin die Sperre A. Die Berechnung fügt natürlich jedes Mal, wenn Sie den Synchronisationsblock beenden, um 1, bis er 0 reduziert wird.
Einige Merkmale von synchronisiert
So ändern Sie den Code
Modifikationsmethode
public class baseclass {public synchronisierte void do () {System.out.println ("is base"); }}Dies bedeutet, eine Methode direkt zu sperren, und Sie müssen beim Eingeben dieses Methodenblocks eine Schloss erhalten.
Ändern Sie Codeblöcke
öffentliche Klasse BaseClass {private static Object lock = new Object (); public void do () {synchronized (lock) {system.out.println ("ist base"); }}}Hier wird der Bereich des Schlosses auf einige Codeblöcke der Methode reduziert, wodurch die Flexibilität des Schlosses verbessert wird. Schließlich ist die Granularitätskontrolle des Schlosses auch ein wichtiges Problem für das Schloss.
Art der Objektschloss
Ich sehe oft, dass einige Codes in besonderer Weise synchronisiert werden und den folgenden Code betrachten:
öffentliche Klasse BaseClass {private static Object lock = new Object (); public void do () {synchronized (lock) {}} public synchronisierte void dovoid () {} public synchronisierte statische void dostaticvoid () {} public static void dosticvoid () {synchronisierte (BaseClass.class) {}}}}}}Hier gibt es vier Situationen: Änderung des Codeblocks, Änderung der Methode, Änderung der statischen Methode und Änderung des Klassenobjekts von BaseClass. Was sind die Unterschiede in diesen Situationen?
Ändern Sie Codeblöcke
In diesem Fall erstellen wir eine Objektschloss mit synchronisiertem (Sperre) im Code, was bedeutet, die integrierte Schloss des Objekts zu verwenden. In diesem Fall wird die Schlossregelung an ein Objekt übergeben. Natürlich gibt es eine andere Möglichkeit, dies zu tun:
public void do () {synchronized (this) {System.out.println ("ist Basis"); }}Mit diesem bedeutet die Sperre des aktuellen Objekts. Der Schlüssel zum integrierten Schloss wird auch hier erwähnt. Ich biete ein Schloss zum Schutz dieses Code. Egal welcher Faden kommt, es wird dem gleichen Schloss ausgesetzt sein.
Methode zum Ändern von Objekten
Wie ist die Situation mit dieser direkten Änderung? Tatsächlich ähnelt es dem Ändern von Codeblöcken, außer dass dies standardmäßig die Sperre des aktuellen Objekts ist. Auf diese Weise ist es relativ einfach und klar, den Code zu schreiben. Wie bereits erwähnt, ist der Unterschied zwischen dem Modifizieren von Codeblöcken hauptsächlich der Unterschied zwischen der Kontrolle der Granularität.
Ändern Sie statische Methoden
Gibt es etwas anderes an statischen Methoden? Es ist in der Tat anders. Das zu diesem Zeitpunkt erworbene Schloss ist nicht mehr dies, und die Klasse, auf die dieses Objekt hingewiesen wird, ist das Klassenschloss. Da die Klasseninformationen in Java in den konstanten Gebiet der Methode geladen werden, ist die globale einzigartig. Dies bietet tatsächlich ein globales Schloss.
Klassenobjekt der modifizierten Klasse
Diese Situation ist tatsächlich sehr ähnlich wie beim Ändern statischer Methoden, aber sie ist immer noch der gleiche Grund. Diese Methode kann eine flexiblere Kontrollkörnigkeit liefern.
Zusammenfassung
Durch die Analyse und das Verständnis dieser Situationen können wir tatsächlich erkennen, dass das Hauptkernkonzept der integrierten Schloss darin besteht, ein Code-Stück mit einem Schloss bereitzustellen, das für gegenseitig ausschließende und eine ähnliche Funktion wie ein Schalter verwendet werden kann.
Java bietet auch einige Implementierungen für integrierte Schlösser. Die Hauptfunktion ist, dass Java alles Objekte ist und jedes Objekt über ein Schloss verfügt, sodass Sie auswählen können, welches Sperre für die Situation verwendet werden soll.
java.util.concurrent.locks.lock
Ich habe mich früher synchron angesehen. In den meisten Fällen ist es fast genug. Das System wird jedoch in gleichzeitiger Programmierung immer komplexer, sodass es immer viele Szenarien gibt, in denen die synchronisierte Verarbeitung schwieriger ist. Oder wie in <java gleichzeitiger Programmierung> Sperrs in gleichzeitiger Ergänzung zu internen Schlössern und bietet fortschrittlichere Funktionen.
Einfache Analyse von java.util.concurrent.locks.lock
Diese Schnittstelle wird den Hauptbetrieb des Schlosses abstrahiert und ermöglicht somit Schlösser, die von der Schloss abgeleitet wurden: bedingungslose, zyklierbare, zeitlich und unterbrechbare. Darüber hinaus werden die Verriegelungs- und Entsperrvorgänge explizit durchgeführt. Hier ist der Code:
public interface lock {void lock (); void lockinterrupticel () wirft InterruptedException aus; boolean trylock (); Boolesche Trylock (lange Zeit, Timeunit Unit) wirft InterruptedException aus; void entsperr (); Bedingung Newcondition ();} Wiedereintritt
Reentrantlock ist ein Wiedereintrittschloss, auch der Name ist so explizit. Reentrantlock bietet eine ähnliche Semantik wie synchronisiert, aber Reentrantlock muss explizit genannt werden, wie z. B.:
öffentliche Klasse BaseClass {private lock lock = new Reentrantlock (); public void do () {lock.lock (); try {// ..} endlich {lock.unlock (); }}}Diese Methode ist für das Lesen von Code ziemlich klar, aber es gibt ein Problem, dh wenn Sie vergessen, schließlich den Versuch hinzuzufügen oder zu vergessen, lock.unlock () zu schreiben, wird das Schloss nicht veröffentlicht, was zu einigen Deadlocks führen kann. Es besteht kein synchronisiertes Risiko.
Trylock
Reentrantlock implantiert die Sperroberfläche und bietet natürlich ihre Funktionen, einschließlich Trylock. Trylock ist zu versuchen, das Schloss zu erwerben. Wenn das Schloss von anderen Threads besetzt ist, wird sofort falsch zurückgegeben. Wenn dies nicht der Fall ist, sollte es besetzt und zurückkehren, was bedeutet, dass das Schloss erhalten wurde.
Eine weitere Trylock -Methode enthält Parameter. Die Funktion dieser Methode besteht darin, eine Zeit anzugeben, was bedeutet, dass Sie weiterhin versuchen, das Schloss während dieser Zeit zu erhalten, und aufgeben, wenn die Zeit nicht erhalten wurde.
Da Trylock nicht immer auf Schlösser blockiert und wartet, kann es das Auftreten von Deadlocks mehr vermeiden.
lockinternrupt
Lockinterrupting reagiert auf Interrupts, wenn Fäden Schlösser erwerben. Wenn ein Interrupt festgestellt wird, wird eine Interrupt -Ausnahme vom Code der oberen Ebene ausgelöst. In diesem Fall wird ein Ausgangsmechanismus für ein Round-Robin-Schloss vorgesehen. Um die unterbrochene Sperroperation besser zu verstehen, wurde eine Demo geschrieben, um sie zu verstehen.
Paket com.test; import java.util.date; import java.util.concurrent public static void main (String [] args) {Thread Thread1 = neuer Thread (neuer Runnable () {@Override public void run () {try {doprint ("thread 1 lock lock. Thread Thread2 = neuer Thread (neuer Runnable () {@Override public void run () {try {doprint ("Thread 2 Get Lock."); Do123 (); Doprint ("Thread 2 end.");} Catch (interruptedException e) {doprint ("Thread 2 wird interrucken.");}}}}); Thread1.SetName ("Thread1"); thread2.setName ("thread2"); Thread1.Start (); Versuchen Sie {Thread.sleep (100); // Warten Sie eine Weile, um Thread1 vor Thread2} catch (InterruptedException e) {e.printstacktrace () auszuführen; } thread2.Start (); } private static void do123 () löscht InterruptedException {lock.lockInterruptisle (); Doprint (thread.currentThread (). getName () + "ist gesperrt."); try {doprint (thread.currentThread (). getName () + "dosoming1 ...."); Thread.Sleep (5000); // Ware einige Sekunden, um die Reihenfolge der Threads Doprint (Thread.currentThread () zu erleichtern. GetName () + "DoSoming2 ...."); Doprint (thread.currentThread (). getName () + "ist fertig."); } endlich {lock.unlock (); }} private static void doprint (String text) {System.out.println ((new Date ()). Tolocalestring () + ":" + text); }}Der obige Code enthält zwei Threads. Thread1 beginnt früher als Thread2. Um den Sperrvorgang zu sehen, wird der gesperrte Code 5 Sekunden lang geschlafen, sodass Sie den Prozess des ersten und zweiten Threads spüren können, der in den Sperrerfassungsprozess eintritt. Das Endergebnis des oben genannten Code ist wie folgt:
2016-9-28 15:12:56: Thread 1 Get Lock.
2016-9-28 15:12:56: Thread1 ist gesperrt.
2016-9-28 15:12:56: Thread1 DoSoming1 ....
2016-9-28 15:12:56: Thread 2 Get Lock.
2016-9-28 15:13:01: Thread1 DoSoming2 ....
2016-9-28 15:13:01: Thread1 ist fertig.
2016-9-28 15:13:01: Thread1 wird entladen.
2016-9-28 15:13:01: Thread2 ist gesperrt.
2016-9-28 15:13:01: Thread2 DoSoming1 ....
2016-9-28 15:13:01: Thread 1 Ende.
2016-9-28 15:13:06: Thread2 DoSoming2 ....
2016-9-28 15:13:06: Thread2 ist fertig.
2016-9-28 15:13:06: Thread2 wird entladen.
2016-9-28 15:13:06: Thread 2 Ende.
Es ist zu sehen, dass Thread1 zuerst das Sperre erhält, und Thread2 erhält auch die Sperre später, aber Thread1 hat es zu diesem Zeitpunkt besetzt, sodass Thread2 die Sperre nicht erhält, bis Thread1 die Sperre veröffentlicht.
** Dieser Code zeigt, dass der Thread hinter lockinruptielly to erwerben muss, um das Schloss zu warten, bis das vorherige Schloss veröffentlicht wird, bevor das Schloss erhalten wird. ** Es gibt jedoch noch keine unterbrochene Funktion, daher wird ein Code hinzugefügt:
thread2.Start (); try {thread.sleep (1000); } catch (interruptedException e) {e.printstacktrace ();} // Interrupt Thread2 in 1 Second Thread2.Interrupt ();Nach dem Start von Thread2 rufen Sie die Interrupt -Methode von Thread2 auf. Ok, führen Sie den Code zuerst aus und sehen Sie die Ergebnisse an:
2016-9-28 15:16:46: Thread 1 Holen Sie sich Schloss.
2016-9-28 15:16:46: Thread1 ist gesperrt.
2016-9-28 15:16:46: Thread1 DoSoming1 ....
2016-9-28 15:16:46: Thread 2 Get Lock.
2016-9-28 15:16:47: Thread 2 wird unterbrochen. <-reagieren Sie direkt auf Thread Interrupt
2016-9-28 15:16:51: Thread1 DoSoming2 ....
2016-9-28 15:16:51: Thread1 ist fertig.
2016-9-28 15:16:51: Thread1 wird entladen.
2016-9-28 15:16:51: Thread 1 Ende.
Im Vergleich zum vorherigen Code ist festgestellt, dass Thread2 darauf wartet, dass Thread1 die Sperre loslegt, aber Thread2 selbst unterbricht, und der Code hinter Thread2 wird weiterhin weiter ausgeführt.
ReadWriteLock
Wie der Name schon sagt, handelt es sich um eine Leseschreibeschloss. Diese Art von Les-Schreiben-Schloss-Anwendungsszenario kann auf diese Weise verstanden werden. Zum Beispiel wird eine Datenwelle hauptsächlich zum Lesen bereitgestellt, und es gibt nur eine relativ geringe Anzahl von Schreibvorgängen. Wenn ein Mutex -Schloss verwendet wird, führt dies zu einer Sperrwettbewerb zwischen Threads. Wenn jeder beim Lesen lesen kann, sperren Sie eine Ressource, sobald sie geschrieben werden soll. Solche Änderungen lösen dieses Problem gut und ermöglichen es dem Lesevorgang, die Leseleistung zu verbessern, ohne den Schreibvorgang zu beeinflussen.
Auf eine Ressource kann von mehreren Lesern oder von einem Schriftsteller zugegriffen werden, und beide können nicht gleichzeitig durchgeführt werden.
Dies ist die abstrakte Schnittstelle zum Lesen und Schreiben von Sperren, Definieren einer Lesesperrung und eines Schreibschloss.
öffentliche Schnittstelle ReadWriteLock { /*** Gibt das für das Lesen verwendete Sperre zurück. * * @return das für das Lesen verwendete Schloss */ Lock readlock (); /*** Gibt das für das Schreiben verwendete Schloss zurück. * * @return das zum Schreiben verwendete Schloss */ lock writeLock ();}In JDK gibt es eine ImplanTreadWriteLock-Implementierung, eine Wiedereintrittsschreiberschloss. ReentranTreadWriteLock kann in zwei Arten aufgebaut werden: fair oder unfair. Wenn er während des Baus nicht explizit angegeben ist, wird standardmäßig eine Nicht-Fair-Sperre erstellt. Im Nicht-Fair-Sperrmodus ist die Reihenfolge des Thread-Zugriffs ungewiss, dh sie kann unterteilt werden. Es kann vom Schriftsteller zum Leser herabgestuft werden, aber der Leser kann nicht auf den Schriftsteller aktualisiert werden.
Wenn es sich um einen fairen Sperrmodus handelt, wird die Option mit der längsten Wartezeit an den Thread übergeben. Wenn ein Lese -Thread die Sperre erhält und ein Schreib -Thread die Schreibschloss anfordert, wird die Read -Sperre -Akquisition nicht mehr empfangen, wenn der Schreibvorgang abgeschlossen ist.
Einfache Codeanalyse behält tatsächlich ein Synchronisierungsschloss in ReentranTreadWriteLock bei, sieht jedoch semantisch aus wie ein Leseschloss und ein Schreibschloss. Schauen Sie sich seinen Konstruktor an:
public ReentranTreadWriteLock (boolean fair) {sync = fair? neuer Fairsync (): neuer Nicht -Fairsync (); Readerlock = new Readlock (this); writerLock = new Writelock (this);} // Der Konstruktor der read -lock -geschützten Readlock (ReentranTreadWriteLock Lock) {sync = lock.sync;} // Der Konstruktor von Write Lock Protected Writelock (ReentranTreadWriteLock Lock) {sync = sync;}}Sie können sehen, dass das Sync -Lock -Objekt von ReentranTreadWriteLock beim Aufbau tatsächlich referenziert wird. Und diese Synchronisierungsklasse ist eine interne Klasse von ReentranTreadWriteLock. Kurz gesagt, Lese-/Schreibschlösser werden alle durch Synchronisierung durchgeführt. Wie arbeitet es mit der Beziehung zwischen den beiden zusammen?
// Verriegelungsmethode zum Lesen von Lock public void lock () {sync.acquireshared (1);} // Verriegelungsmethode zum Schreiben von Lock public void lock () {sync.acquire (1);}Der Hauptunterschied besteht darin, dass das Read Lock ein gemeinsames Schloss erhält, während das Schreibschloss ein exklusives Schloss erwirbt. Es gibt hier einen Punkt, der erwähnt werden kann, dh, um sicherzustellen, dass ReentranTeadWriteLock sichergestellt wird, müssen sowohl gemeinsame und exklusive Schlösser die Haltezahlen und Wiedereintritte unterstützen. Reentrantlock wird mit dem Zustand gespeichert, und Status kann nur einen Formungswert speichern. Um mit dem Problem von zwei Schlössern kompatibel zu sein, ist es in die Anzahl der Threads unterteilt, die die gemeinsame Sperre oder die Anzahl der Threads, die die ausschließliche Sperre oder die Anzahl der Wiedereintrittsqualitäten halten, enthalten.
andere
Ich schrieb einen großen Artikel, den ich für zu schreiben hatte, und es gibt einige nützliche Schlösser:
Countdownlatch
Es soll einen Zähler festlegen, der gleichzeitig gehalten wird. Wenn der Anrufer die wartende Methode des Countdownlatch aufruft, blockiert er, wenn der aktuelle Zähler nicht 0 ist. Wenn Sie die Release -Methode des Countdownlatch aufrufen, kann die Anzahl reduzieren, bis der Anrufer, der aufgerufen wird, aufbaut.
Semaphon
Semaphor ist eine Form der Autorisierung und Lizenz, z. B. 100 Lizenzen, sodass 100 Threads gleichzeitig Schlösser halten können und wenn dieser Betrag überschreitet, wird dies zum Fehler zurückkehren.
Vielen Dank, dass Sie diesen Artikel gelesen haben, ich hoffe, er kann Ihnen helfen. Vielen Dank für Ihre Unterstützung für diese Seite!