0. Über Mutex
Das sogenannte Mutex-Schloss bezieht sich auf ein Schloss, das jeweils nur einen Faden haben kann. Vor JDK1.5 verwendeten wir normalerweise den synchronisierten Mechanismus, um den Zugriff mehrerer Threads auf gemeinsame Ressourcen zu steuern. Jetzt bietet Lock eine breitere Auswahl an Sperrvorgängen als der synchronisierte Mechanismus. Die Hauptunterschiede zwischen Sperr- und synchronisierten Mechanismen:
Der synchronisierte Mechanismus bietet Zugriff auf implizite Monitorschlösser, die jedem Objekt zugeordnet sind, und erzwingt die gesamte Sperraufnahme und -freisetzungen, um in einer Blockstruktur zu erscheinen. Wenn mehrere Schlösser erworben werden, müssen sie in umgekehrter Reihenfolge freigegeben werden. Der synchronisierte Mechanismus setzt die Schlösser implizit frei. Solange der vom Thread ausgeführte Code den Umfang des synchronisierten Anweisungsblocks überschreitet, wird die Sperre veröffentlicht. Der Sperrmechanismus muss explizit die entsperr () -Methode des Sperrobjekts aufrufen, um die Sperre freizusetzen, die die Möglichkeit für die Erfassung und Freisetzung der Sperren bietet, um nicht in derselben Blockstruktur zu erscheinen, und die Sperren in einer freieren Reihenfolge freizugeben.
1. Einführung in Reentrantlocke
Reentrantlock ist eine Wiedereintritts -Mutex -Sperre, die auch als "exklusives Schloss" bezeichnet wird.
Wie der Name schon sagt, kann eine Wiedereintrittschloss nur von einer Fadensperrung zum gleichen Zeitpunkt gehalten werden. Während Wiedereintritt bedeutet, dass eine Wiedereintrittsschloss mehrmals von einem einzelnen Thread erfasst werden kann.
Reentrantlock ist in "Fair Lock" und "Unfair Lock" unterteilt. Ihre Unterschiede spiegeln sich darin wider, ob der Mechanismus, Schlösser zu erhalten, fair ist. "Lock" soll konkurrierende Ressourcen schützen und verhindern, dass mehrere Threads gleichzeitig und Fehler betrieben werden. Reentrantlock kann nur von einem Thread gleichzeitig erworben werden (wenn ein Thread das "Sperre" erwerbt, müssen andere Threads warten). ReStraantlock verwaltet alle Threads, die das Schloss durch eine FIFO -Warteschlange erwerben. Unter dem Mechanismus von "Fair Lock" stehen die Gewinde, um das Schloss nacheinander zu erwerben. Während "Nicht-Fair-Sperre" das Schloss erwerben, unabhängig davon, ob es sich am Anfang der Warteschlange befindet oder nicht.
Reentrantlock -Funktionsliste
// Erstellen Sie ein Wiedereintrittslock, das standardmäßig "unlauteres Schloss" ist. Reentrantlock () // Die Erstellungsrichtlinie ist das Reentrantlock der Messe. Wenn fair wahr ist, bedeutet es, dass es sich um ein faires Schloss handelt, und wenn fair falsch ist, bedeutet es, dass es sich um ein Nicht-Fair-Schloss handelt. Reentrantlock (Boolean Fair) // Abfragen Sie die Häufigkeit, mit der der aktuelle Thread dieses Sperre gepflegt hat. Int GetholdCount () // gibt den Thread, der derzeit dieses Sperre besitzt, zurück. Protected Thread Getowner () // gibt eine Sammlung zurück, die den Thread enthält, der möglicherweise darauf wartet, dieses Schloss zu erwerben. Protected Collection <Thread> getQuEedThreads () // gibt die geschätzte Anzahl von Threads zurück, die darauf warten, dieses Schloss zu erwerben. int getQueelength () // gibt eine Sammlung zurück, die die Threads enthält, die auf einen bestimmten Zustand im Zusammenhang mit dieser Sperre warten können. Protected Collection <Thread> GetwaitingThreads (Zustandszustand) // Gibt die Threadschätzung zurück, die auf die angegebene Bedingung wartet, die dieser Sperre zugeordnet ist. int getwaitqueelength (Zustand Zustand) // Fragen Sie an, ob der angegebene Thread darauf wartet, dieses Schloss zu erwerben. boolean hasqueuedthread (Thread Thread) // Fragen Sie ab, ob einige Threads darauf warten, dieses Schloss zu erwerben. boolean hasqueuedthreads () // Fragen Sie an, ob einige Threads auf eine bestimmte Erkrankung im Zusammenhang mit diesem Schloss warten. Boolesche Haswaiters (Zustandsbedingung) // RECHT TRUE, wenn es sich um "faire Sperre" handelt, ansonsten geben Sie false zurück. boolean isfair () // Fragen Sie ab, ob der aktuelle Thread dieses Schloss beibehält. boolean isheldbycurrentThread () // Fragen Sie an, ob dieses Schloss von einem Thread gehalten wird. boolean islocked () // das Schloss erhalten. void lock () // Wenn der aktuelle Gewinde nicht unterbrochen ist, wird das Schloss erworben. void lockinterrupticable () // gibt die Bedingungsinstanz zurück, die für diese Sperrinstanz verwendet wird. Bedingung Newcondition () // Erwerben Sie das Schloss nur, wenn es während des Anrufs nicht von einem anderen Thread gehalten wird. boolean trylock () // Wenn das Schloss nicht von einem anderen Thread innerhalb einer bestimmten Wartezeit gehalten wird und der aktuelle Thread nicht unterbrochen wird, wird das Schloss erworben. Boolean Trylock (langfristig, Zeiteinheit) // Versuch, dieses Schloss zu veröffentlichen. void entsperr ()
2. Beispiel für Wiedereintritt
Durch den Vergleich von "Beispiel 1" und "Beispiel 2" können wir die Rolle von Lock und Freischaltung klar verstehen
2.1 Beispiel 1
importieren java.util.concurrent.locks.lock; import Java.util.concurrent.locks.reentrantlock; // locktest1.java// Repository Class Depot {private int size; // die tatsächliche Anzahl der privaten Sperren von Repository; // Exclusive Lock Public Depot () {this.size = 0; this.lock = new Reentrantlock (); } public void produc (int val) {lock.lock (); Versuchen Sie {size += val; System.out.printf ("%s produzieren (%d) -> size =%d/n", thread.currentThread (). GetName (), val, Größe); } endlich {lock.unlock (); }} public void Consumum (int val) {lock.lock (); Versuchen Sie {size -= val; System.out.printf ("%s Konsum (%d) <- size =%d/n", Thread.currentThread (). GetName (), val, Größe); } endlich {lock.unlock (); }}}; // Produzentenklassenproduzent {private Depot -Einzahlung; öffentlicher Produzent (Depot -Depot) {this.depot = deposit; } // Konsumgüterprodukte: Erstellen Sie einen neuen Thread, um Produkte in das Lager zu produzieren. public void produc (endgültig int val) {new thread () {public void run () {dough.Produce (val); } }.Start(); }} // Verbraucherklasse Kunde {private Depot -Einzahlung; public customer (depot depot) {this.depot = depon; } // Verbraucherprodukt: Erstellen Sie einen neuen Thread, um Produkt aus dem Lagerhaus zu konsumieren. public void Consumption (endgültig int val) {neuer Thread () {public void run () {depot.consume (val); } }.Start(); }} public class lockTest1 {public static void main (String [] args) {depot mdepot = new depot (); Produzent mpro = neuer Produzent (Mdepot); Kunde MCUS = neuer Kunde (Mdepot); Mpro.Produce (60); Mpro.Produce (120); mcus.consume (90); mcus.consume (150); Mpro.Produce (110); }} Auslaufergebnisse:
Thread-0-Erzeugnisse (60)-> Größe = 60 Thread-1-Produkte (120)-> Größe = 180 Thread-3 Consume (150) <-Größe = 30Thread-2 Consume (90) <-Größe = -60Thread-4 produzieren (110)-> Größe = 50
Ergebnisanalyse:
(1) Depot ist ein Lagerhaus. Die Waren können durch Produce () in das Lagerhaus hergestellt werden, und die Waren im Lager können durch den Konsum () konsumiert werden. Der sich gegenseitig ausschließende Zugang zum Lagerhaus wird durch die exklusive Schloss erreicht: Vor dem Betrieb der Waren im Lager (Produktion/Verbrauch) wird das Lagerhaus zuerst durch Lock () gesperrt und dann nach Abschluss des Betriebs durch Entsperren () entsperrt.
(2) Produzent ist Produzent. Wenn Sie die Funktion produzieren () im Hersteller aufrufen, können Sie einen neuen Thread erstellen, um Produkte im Lager zu produzieren.
(3) Kunde ist eine Verbraucherkategorie. Wenn Sie die Funktion Consumption () im Kunden aufrufen, können Sie ein neues Thread -Verbrauchsprodukt im Lager erstellen.
(4) Im Haupt -Thread -Main werden wir einen neuen Produzenten Mpro und einen neuen Verbraucher -MCUs erstellen. Sie produzieren/Konsumprodukte in Lagerhäuser.
Gemäß der Produktions-/Verbrauchsmenge im Main sollte das endgültige verbleibende Produkt im Lager 50 sein. Die Ergebnisse des Betriebs entsprechen unseren Erwartungen!
Es gibt zwei Probleme mit diesem Modell:
(1) In Wirklichkeit kann die Kapazität des Lagerhauses nicht negativ sein. Die Lagerkapazität in diesem Modell kann jedoch negativ sein, was der Realität widerspricht!
(2) In Wirklichkeit ist die Kapazität des Lagerhauses begrenzt. Die Kapazität in diesem Modell gibt jedoch wirklich keine Begrenzung!
Wir werden kurz darüber sprechen, wie diese beiden Probleme gelöst werden können. Schauen wir uns nun zuerst ein einfaches Beispiel 2 an. Durch den Vergleich von "Beispiel 1" und "Beispiel 2" können wir den Zweck von lock () und entsperren () klarer verstehen.
2.2 Beispiel 2
importieren java.util.concurrent // die tatsächliche Anzahl der privaten Sperren von Repository; // Exclusive Lock Public Depot () {this.size = 0; this.lock = new Reentrantlock (); } public void produc (int val) {// lock.lock (); // try {size += val; System.out.printf ("%s produzieren (%d) -> size =%d/n", thread.currentThread (). GetName (), val, Größe); //} catch (interruptedException e) {//} schließlich {// lock.unlock (); //} public void conss (int val) {// // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // //.lock (); System.out.printf ("%s conseum (%d) <- size =%d/n", Thread.currentThread (). GetName (), val, size); //} schließlich {// lock.unLock (); //}}}; // Produzent-Klassenproduzent {private Depoteinlagerung; öffentlicher Produzent (Depot -Einzahlung) {this.depot = deposit; } // Verbraucherprodukt: Erstellen Sie einen neuen Thread, um das Produkt in das Lagerhaus zu produzieren. public void produc (endgültig int val) {new thread () {public void run () {dough.Produce (val); } }.Start(); }} // Verbraucherklasse Kunde {private Depot -Einzahlung; public customer (depot depot) {this.depot = depon; } // Verbraucherprodukt: Erstellen Sie einen neuen Thread, um Produkt aus dem Lagerhaus zu konsumieren. public void Consumption (endgültig int val) {neuer Thread () {public void run () {depot.consume (val); } }.Start(); }} public class lockTest2 {public static void main (String [] args) {depot mdepot = new depot (); Produzent mpro = neuer Produzent (Mdepot); Kunde MCUS = neuer Kunde (Mdepot); Mpro.Produce (60); Mpro.Produce (120); mcus.consume (90); mcus.consume (150); Mpro.Produce (110); }} (Einmal) Ergebnis:
Thread-0-Produkte (60)-> Größe = -60 Thread-4-Erzeugnisse (110)-> Größe = 50 Thread-2-Verbrauch (90) <-Größe = -60 Thread-1 produzieren (120)-> Größe = -60 Thread-3-Verbrauch (150) <-size = -60
Ergebnisse Beschreibung:
"Beispiel 2" entfernt die Sperre basierend auf "Beispiel 1". In Beispiel 2 beträgt das endgültige verbleibende Produkt im Lagerhaus -60, nicht die 50, die wir erwartet haben. Der Grund dafür ist, dass wir den MUTEX -Zugriff auf das Repository nicht implementieren.
2.3 Beispiel 3
In "Beispiel 3" verwenden wir den Zustand, um zwei Probleme in "Beispiel 1" zu lösen: "Die Kapazität des Lagerhauses kann nicht negativ sein" und "die Kapazität des Lagerhauses ist begrenzt".
Die Lösung für dieses Problem ist der Zustand. Die Bedingung muss in Verbindung mit der Sperre verwendet werden: Die Warte () -Methode im Zustand kann dazu führen, dass der Gewinde [ähnlich wie Wait ()] blockiert. Die Signal () -Konstruktionsmethode kann dazu führen, dass der Weckfaden [ähnlich zu benachrichtigen ()].
Import java.util.concurrent.locks.lock; import java.util.concurrent.locks.reentrantlock; import java.util.concurrent // Lagerkapazität Privat int Größe; // die tatsächliche Anzahl von Warehouse Private Lock Lock; // Exklusive Sperre Private Condition FullCondition; // Produktionsbedingungen Privatbedingungen leere Kondition; // Verbrauchsbedingungen öffentliches Depot (intkapazität) {this.capacity = Kapazität; this.size = 0; this.lock = new Reentrantlock (); this.fullCondion = lock.newcondition (); this.emptycondition = lock.newcondition (); } public void produc (int val) {lock.lock (); Versuchen Sie, {// links bedeutet "die Menge, die Sie produzieren möchten" (es kann zu viel Produktion sein, sodass Sie mehr produzieren müssen) int links = val; Während (links> 0) {// Wenn das Inventar voll ist, warten Sie, bis der "Verbraucher" das Produkt verbraucht. while (size> = Kapazität) fullCondtion.aait (); // Erhalten Sie "tatsächliche Produktionsmenge" (d. H. Die neue Menge, die im Inventar hinzugefügt wird) // Wenn "Inventar" + "gewünschte Produktionsmenge"> "Gesamtkapazität", dann "tatsächliche Inkrement" = "Gesamtkapazität" - "Aktuelle Kapazität". (Füllen Sie das Lagerhaus zu diesem Zeitpunkt aus) // ansonsten "tatsächliche Inkrement" = "Die Menge, die Sie produzieren möchten" inc = (Größe+links)> Kapazität? (Kapazitätsgröße): links; Größe += Inc; links -= Inc; System.out.printf ("%s produzieren (%3D) -> links =%3D, Inc =%3D, Größe =%3D/N", Thread.CurrentThread (). GetName (), val, links, Inc, Größe); // "Verbraucher" benachrichtigen, die Sie konsumieren können. leereCondion.Signal (); }} catch (InterruptedException e) {} endlich {lock.unlock (); }} public void Consumum (int val) {lock.lock (); Versuchen Sie, {// links bedeutet "die Menge des zu konsumierten Verbrauchs" (es kann zu groß sein, das Inventar reicht nicht aus, sodass Sie mehr konsumieren müssen) int links = val; Während (links> 0) {// Wenn das Inventar 0 ist, warten Sie, bis der "Produzent" das Produkt produziert. while (Größe <= 0) leereCondtion.await (); // Erhalten Sie "tatsächliche Verbrauchsmenge" (d. H. Die tatsächliche Abnahme des Inventars) // Wenn "Inventar" <"<" die Menge, die der Kunde konsumieren möchte ", dann" tatsächlicher Verbrauch "=" Inventory "; // Ansonsten "tatsächlicher Verbrauch" = "Die Menge, die der Kunde konsumieren möchte". int dec = (Größe <links)? Größe: links; Größe -= dec; links -= dec; System.out.printf ("%s konsum (%3D) <- links =%3D, dec =%3D, Größe =%3D/N", Thread.CurrentThread (). GetName (), val, links, dec, Größe); fullCondion.Signal (); }} catch (InterruptedException e) {} endlich {lock.unlock (); }} public String toString () {return "Kapazität:"+Kapazität+", tatsächliche Größe:"+Größe; }}; // Produzentenklassenproduzent {private depot depot; öffentlicher Produzent (Depot -Einzahlung) {this.depot = deposit; } // Verbraucherprodukt: Erstellen Sie einen neuen Thread, um das Produkt in das Lagerhaus zu produzieren. public void produc (endgültig int val) {new thread () {public void run () {dough.Produce (val); } }.Start(); }} // Verbraucherklasse Kunde {private Depot -Einzahlung; public customer (depot depot) {this.depot = depon; } // Verbraucherprodukt: Erstellen Sie einen neuen Thread, um Produkt aus dem Lagerhaus zu konsumieren. public void Consumption (endgültig int val) {neuer Thread () {public void run () {depot.consume (val); } }.Start(); }} public class lockTest3 {public static void main (String [] args) {depot mdepot = new depot (100); Produzent mpro = neuer Produzent (Mdepot); Kunde MCUS = neuer Kunde (Mdepot); Mpro.Produce (60); Mpro.Produce (120); mcus.consume (90); mcus.consume (150); Mpro.Produce (110); }} (Einmal) Ergebnis:
Thread-0 produzieren (60)-> links = 0, Inc = 60, Größe = 60 Thread-1 (120)-> links = 80, Inc = 40, Größe = 100 Thread-2-Verbrauch (90) <-links = 0, dec = 90, Größe = 10Thread-3-Verbrauch (150) <-links = 140, dec = 10Thrad = 0Thread-4-Produkte (110) (110)-110)-110)-110)--produziert (110). Verbrauch (150) <-links = 40, dec = 100, Größe = 0Thread-4 produzieren (110)-> links = 0, Inc = 10, Größe = 10Thread-3 Consume (150) <-links = 30, dec = 10, Größe = 0Thread-1 produzieren (120)-> links = 0, Inc = 80, Größe = 80 Thread = 80).