Kursinhalt
Multithreading in Java
Multithreading in Java
Sperre und Bedingung
In Java basiert die Standard-Synchronisation auf dem synchronized
Schlüsselwort und eingebauten Monitorobjekten. In einigen Fällen kann synchronized jedoch nicht ausreichend sein, insbesondere wenn größere Flexibilität im Thread-Management erforderlich ist.
Allgemeine Beschreibung
Das Lock
-Interface und das Condition
-Interface, eingeführt im java.util.concurrent.locks
Paket, bieten erweiterte Thread-Management-Fähigkeiten.
In diesem Bild können Sie sehen, dass der erste Thread das Lock mit der lock()
Methode erfasst, und in diesem Moment kann ein anderer Thread dasselbe Lock nicht erfassen. Sobald der gesamte Code innerhalb des Locks ausgeführt ist, wird die unlock()
Methode aufgerufen und das Lock freigegeben. Erst danach kann der zweite Thread das Lock erfassen.
Unterschied
Der Unterschied zwischen diesen beiden Schnittstellen besteht darin, dass die Lock
-Implementierungen eine High-Level-Alternative zum synchronisierten Block sind, und die Condition
-Schnittstellen-Implementierungen eine Alternative zu den notify()
/wait()
-Methoden darstellen. Beide Schnittstellen sind Teil des java.util.concurrent.locks
-Pakets.
Beispiele aus dem echten Leben
Stellen Sie sich vor, Sie verwalten eine Warteschlange für die Registrierung für eine Veranstaltung. Um ein Überlaufen zu verhindern und eine ordnungsgemäße Sitzplatzvergabe sicherzustellen, müssen Sie Blockierungsmechanismen und Bedingungen verwenden, um den Fluss neuer Registrierungen zu stoppen, bis ein freier Sitzplatz verfügbar wird.
ReentrantLock-Klasse
Die ReentrantLock
-Klasse aus dem java.util.concurrent.locks
-Paket ist eine Implementierung der Lock
-Schnittstelle. Sie bietet Funktionalitäten zur expliziten Verwaltung von Sperren.
Die Hauptmethoden von ReentrantLock:
lock()
: Erfasst eine Sperre;unlock()
: Gibt die Sperre frei;tryLock()
: Versucht, die Sperre zu erfassen, und gibt true zurück, wenn die Erfassung erfolgreich ist;tryLock(long timeout, TimeUnit unit)
: Versucht, die Sperre für die angegebene Zeit zu erfassen;newCondition()
: Erstellt eine Bedingung für die aktuelleLock
.
Main
package com.example; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Main { // Creating a ReentrantLock object private final Lock lock = new ReentrantLock(); private int count = 0; // Method to increment the `count` variable public void increment() { lock.lock(); // Acquiring the `lock` try { count++; System.out.println("Count incremented to: " + count); } finally { lock.unlock(); // Releasing the `lock` } } public static void main(String[] args) { Main example = new Main(); // Runnable task to call the increment method Runnable task = example::increment; // Starting multiple threads to execute the task for (int i = 0; i < 5; i++) { new Thread(task).start(); } } }
Wie Sie sehen können, verwenden wir im increment()
-Methode das Sperren mit Lock
. Wenn ein Thread die Methode betritt, erfasst er die Sperre in lock.lock()
und führt dann den Code aus. Im finally-Block geben wir die Sperre in lock.unlock()
frei, damit andere Threads eintreten können.
Wir geben die Sperre im finally
-Block aus einem bestimmten Grund frei, nämlich weil dieser Block fast immer ausgeführt wird, selbst bei Ausnahmen, außer wenn wir das Programm beenden.
Condition-Schnittstelle
Wir können nur eine Condition
mit einer Bindung an eine spezifische Lock
-Implementierung erstellen. Aus diesem Grund werden die Condition
-Methoden nur die Sperrung dieser bestimmten Lock
-Implementierung beeinflussen.
Main
private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition();
Die Hauptmethoden von Condition:
await()
: Wartet auf ein Signal von einem anderen Thread;signal()
: Entsperrt einen Thread, der auf eine Bedingung wartet;signalAll()
: Entsperrt alle Threads, die auf die Bedingung warten.
Main
private final ReentrantLock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); private boolean ready = false; public void waitForCondition() throws InterruptedException { lock.lock(); // Acquire the `lock` try { while (!ready) { // Check if the condition is not met System.out.println("Waiting..."); // Print a waiting message condition.await(); // Wait for the condition to be signaled } System.out.println("Condition met!"); // Print a message when the condition is met } finally { lock.unlock(); // Release the `lock` } }
Die waitForCondition()
Methode blockiert den Thread, bis die Variable ready
true wird, was signalisiert, dass die Bedingung erfüllt ist. Wenn die Bedingung erfüllt ist, läuft der Thread weiter und zeigt die Nachricht „Bedingung erfüllt!“ an.
Hinweis
Wenn die Methode
await()
aufgerufen wird, wird der Thread pausiert und gibt auch das erfasste Schloss frei. Wenn der Thread aufwacht, sollte er das Schloss erneut erfassen und erst dann wird er mit der Ausführung beginnen!
Codebeispiel
Schauen wir uns nun ein Beispiel für die Verwendung von ReentrantLock
und Condition
zur Verwaltung der Registrierung für ein Ereignis an:
Ein kurzer Clip aus dem Video
Sperren mit ReentrantLock: Die register()
-Methode erfasst die Sperre mit lock.lock()
, um zu verhindern, dass mehrere Threads gleichzeitig Code ausführen.
Bedingung mit Condition: Wenn keine verfügbaren Plätze vorhanden sind, ruft der Thread spaceAvailable.await()
auf, um zu warten, bis Platz verfügbar ist.
Freigeben des Locks: Wenn ein Thread Platz mit der Methode cancel()
freigegeben hat, ruft er spaceAvailable.signalAll()
auf, um alle wartenden Threads zu benachrichtigen.
Ausnahmebehandlung: Die Verwendung von try-finally
-Blöcken stellt sicher, dass das Schloss freigegeben wird, selbst wenn eine Ausnahme auftritt.
Fazit
Der Einsatz von
Lock
undCondition
in Java ermöglicht eine flexiblere Kontrolle über Threads und Synchronisation als der traditionelle synchronized-Mechanismus. Dies ist besonders nützlich in komplexen Szenarien, in denen eine präzisere Kontrolle über Threads und Wartebedingungen erforderlich ist.
Danke für Ihr Feedback!