Heim >Java >javaLernprogramm >Was sind die verschiedenen Sperren in Java?
Einführung in 15 Arten von Sperren in Java
Beim Lesen vieler Parallelitätsartikel werden verschiedene Sperren erwähnt, z. B. faire Sperren, optimistische Sperren usw. In diesem Artikel wird die Klassifizierung verschiedener Sperren vorgestellt. Der Inhalt der Einleitung lautet wie folgt:
Faire Sperre / unfaire Sperre
Wiedereintrittssperre/Nichtwiedereintrittssperre
Exklusive Sperre / gemeinsame Sperre
Mutex-Sperre / Lese-/Schreibsperre
Optimistische Sperre / pessimistische Sperre
Segmentiertes Schloss
Schrägschloss / Leichtgewichtschloss / Schwergewichtschloss
Spin-Lock
Die oben genannten Nomen beziehen sich nicht alle auf den Status des Schlosses, andere beziehen sich auf das Design des Schlosses Substantiv.
Faire Sperre / unfaire Sperre
Faires Schloss
Faire Sperre bedeutet, dass mehrere Threads Sperren in der Reihenfolge erhalten, in der sie Sperren beantragen.
Unfaire Sperre
Eine unfaire Sperre bedeutet, dass die Reihenfolge, in der mehrere Threads Sperren erhalten, nicht mit der Reihenfolge übereinstimmt, in der sie Sperren anwenden. Es ist möglich, dass der Thread, der später die Sperre anwendet, die Sperre erhält, bevor der Thread, der zuerst anwendet. Es ist möglich, dass es zu einer Prioritätsumkehr oder einem Aushungern kommt.
Für Java ReentrantLock geben Sie über den Konstruktor an, ob die Sperre eine faire Sperre ist, und der Standardwert ist eine unfaire Sperre. Der Vorteil unfairer Sperren besteht darin, dass der Durchsatz größer ist als bei fairen Sperren. Für Synchronized handelt es sich ebenfalls um eine unfaire Sperre. Da es keine Thread-Planung über AQS wie ReentrantLock implementiert, gibt es keine Möglichkeit, es in eine faire Sperre umzuwandeln.
Wiedereintrittssperre/Nichtwiedereintrittssperre
Wiedereintrittssperre
Eine wiedereintrittsfähige Sperre im weitesten Sinne bezieht sich auf eine Sperre, die wiederholt und rekursiv aufgerufen werden kann. Nachdem die Sperre in der äußeren Schicht verwendet wurde, kann sie weiterhin in der inneren Schicht ohne Deadlock verwendet werden (vorausgesetzt, es handelt sich um dasselbe Objekt oder dieselbe Klasse). ). Eine solche Sperre wird als Wiedereintrittssperre bezeichnet. ReentrantLock und synchronisiert sind beide Wiedereintrittssperren
synchronisiert void setA() löst eine Ausnahme aus{
Thread.sleep(1000);
setB();
}
synchronisiertes void setB() löst eine Ausnahme aus{
Thread.sleep(1000);
}
Der obige Code ist eine Funktion einer Wiedereintrittssperre. Wenn es sich nicht um eine Wiedereintrittssperre handelt, wird setB möglicherweise nicht vom aktuellen Thread ausgeführt, was zu einem Deadlock führen kann.
Keine Wiedereintrittssperre
Nicht wiedereintrittsfähige Sperren können im Gegensatz zu wiedereintrittsfähigen Sperren nicht rekursiv aufgerufen werden, und es kommt zu einem Deadlock, wenn rekursive Aufrufe durchgeführt werden. Ich habe eine klassische Erklärung für die Verwendung einer Spin-Sperre zum Simulieren einer nicht wiedereintretenden Sperre gesehen. Der Code lautet wie folgt
java.util.concurrent.atomic.AtomicReference importieren;
öffentliche Klasse UnreentrantLock {
private AtomicReference
public void lock() {
Thread current = Thread.currentThread();
//Dieser Satz ist eine sehr klassische „Spin“-Syntax, die auch in AtomicInteger
zu finden ist für (;;) {
if (!owner.compareAndSet(null, current)) {
zurück;
}
}
}
public void unlock() {
Thread current = Thread.currentThread();
owner.compareAndSet(current, null);
}
}
Der Code ist auch relativ einfach. Er verwendet atomare Referenzen, um Threads zu speichern. Wenn unlock() nicht zweimal ausgeführt wird, kommt es beim zweiten Aufruf zu einem Deadlock Diese Sperre ist nicht wiedereintrittsfähig, aber tatsächlich muss nicht jedes Mal derselbe Thread die Sperre aufheben und die Sperre erwerben.
Verwandeln Sie es in eine Wiedereintrittssperre:
java.util.concurrent.atomic.AtomicReference importieren;
öffentliche Klasse UnreentrantLock {
private AtomicReference
privater int-Zustand = 0;
public void lock() {
Thread current = Thread.currentThread();
if (current ==owner.get()) {
Zustand++;
zurück;
}
//Dieser Satz ist eine sehr klassische „Spin“-Syntax, die auch in AtomicInteger
zu finden ist für (;;) {
if (!owner.compareAndSet(null, current)) {
zurück;
}
}
}
public void unlock() {
Thread current = Thread.currentThread();
if (current ==owner.get()) {
if (state != 0) {
Staat--;
} sonst {
owner.compareAndSet(current, null);
}
}
}
}
Stellen Sie vor der Ausführung jedes Vorgangs fest, ob der aktuelle Sperreninhaber das aktuelle Objekt ist, und verwenden Sie die Zustandszählung, anstatt die Sperre jedes Mal aufzuheben.
Implementierung der Reentrant-Sperre in ReentrantLock
Hier ist ein Blick auf die Sperrerwerbsmethode für unfaire Sperren:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, Acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//Das ist es
else if (current == getExclusiveOwnerThread()) {
int nextc = c + Acquires;
if (nextc < 0) // Überlauf
Einen neuen Fehler auslösen(„Maximale Sperranzahl überschritten“);
setState(nextc);
return true;
}
return false;
}
In AQS wird ein privater flüchtiger int-Zustand verwaltet, um die Anzahl der Wiedereintritte zu zählen und häufige Halte- und Freigabevorgänge zu vermeiden, was nicht nur die Effizienz verbessert, sondern auch Deadlocks vermeidet.
Exklusive Sperre / gemeinsame Sperre
Exklusive Sperren und gemeinsame Sperren Wenn Sie ReeReentrantLock und ReentrantReadWriteLock im C.U.T-Paket lesen, werden Sie feststellen, dass es sich bei einer davon um eine exklusive Sperre und bei der anderen um eine gemeinsam genutzte Sperre handelt.
Exklusive Sperre: Diese Sperre kann jeweils nur von einem Thread gehalten werden.
Gemeinsame Sperre: Diese Sperre kann von mehreren Threads gemeinsam genutzt werden. Die Lesesperre in ReentrantReadWriteLock kann jedoch jeweils nur exklusiv sein.
Darüber hinaus kann durch die gemeinsame Nutzung von Lesesperren sichergestellt werden, dass das gleichzeitige Lesen sehr effizient ist, sich Lesen und Schreiben sowie Schreiben und Lesen jedoch gegenseitig ausschließen.
Über AQS werden auch exklusive Sperren und gemeinsam genutzte Sperren implementiert, und es werden verschiedene Methoden implementiert, um exklusive oder gemeinsam genutzte Sperren zu erreichen. Für Synchronized handelt es sich natürlich um eine exklusive Sperre.
Mutex-Sperre / Lese-/Schreibsperre
Mutex-Sperre
Sperren Sie die freigegebene Ressource, bevor Sie darauf zugreifen, und entsperren Sie sie, nachdem der Zugriff abgeschlossen ist. Nach dem Sperren wird jeder andere Thread, der versucht, erneut zu sperren, blockiert, bis der aktuelle Prozess entsperrt wird.
Wenn beim Entsperren mehr als ein Thread blockiert ist, werden alle Threads auf der Sperre in den Bereitschaftszustand programmiert. Der erste Thread, der bereit wird, führt den Sperrvorgang aus und die anderen Threads warten erneut. Auf diese Weise kann nur ein Thread auf die durch den Mutex geschützte Ressource zugreifen
Lese-/Schreibsperre
Die Lese-/Schreibsperre ist sowohl eine Mutex-Sperre als auch eine gemeinsame Sperre. Der Lesemodus wird gemeinsam genutzt und der Schreibmodus schließt sich gegenseitig aus (exklusive Sperre).
Es gibt drei Zustände der Lese-/Schreibsperre: Lesesperre, Schreibsperre und entsperrter Zustand
Die spezifische Implementierung der Lese-/Schreibsperre in Java ist ReadWriteLock
Es kann jeweils nur ein Thread eine Lese-/Schreibsperre im Schreibmodus halten, aber mehrere Threads können gleichzeitig eine Lese-/Schreibsperre im Lesemodus halten. Nur ein Thread kann die Schreibstatussperre belegen, aber mehrere Threads können gleichzeitig die Lesestatussperre belegen, wodurch eine hohe Parallelität erreicht werden kann. Wenn er sich unter einer Schreibstatussperre befindet, wird jeder Thread, der die Sperre erhalten möchte, blockiert, bis die Schreibstatussperre aufgehoben wird. Wenn er sich unter einer Lesestatussperre befindet, dürfen andere Threads seine Lesestatussperre erhalten, sind es aber Es ist nicht gestattet, die Schreibstatussperre zu erhalten, bis die Lesestatussperre aller Threads aufgehoben wird, um zu verhindern, dass Threads, die Schreibvorgänge ausführen möchten, die Schreibstatussperre erhalten, wenn die Lese-/Schreibsperre erkennt, dass ein Thread dies wünscht Um die Schreibstatussperre zu erhalten, werden alle nachfolgenden Threads blockiert, die die Lesestatussperre erhalten möchten. Daher eignen sich Lese-/Schreibsperren sehr gut für Situationen, in denen weitaus mehr Lesevorgänge auf Ressourcen als Schreibvorgänge stattfinden.
Optimistische Sperre / pessimistische Sperre
Pessimistische Sperre
Gehen Sie immer vom schlimmsten Fall aus, wenn Sie die Daten abrufen, und denken Sie, dass andere sie ändern werden. Daher werden Sie sie jedes Mal sperren, wenn Sie die Daten abrufen möchten Blockieren, bis sie die Sperre erhalten (gemeinsame Ressource) Wird jeweils nur von einem Thread verwendet, andere Threads werden blockiert und die Ressourcen werden nach der Verwendung an andere Threads übertragen). In herkömmlichen relationalen Datenbanken werden viele solcher Sperrmechanismen verwendet, z. B. Zeilensperren, Tabellensperren, Lesesperren, Schreibsperren usw., die alle vor Operationen gesperrt werden. Exklusive Sperren wie synchronisiert und ReentrantLock in Java sind die Umsetzung der pessimistischen Sperridee.
Optimistisches Schloss
Gehen Sie immer von der besten Situation aus, wenn Sie die Daten abrufen, und denken Sie, dass andere sie nicht ändern werden. Bei der Aktualisierung werden Sie jedoch beurteilen, ob andere die Daten in diesem Zeitraum aktualisiert haben kann den Versionsnummernmechanismus und die CAS-Algorithmusimplementierung verwenden. Die optimistische Sperre eignet sich für Anwendungstypen mit mehreren Lesevorgängen, wodurch der Durchsatz verbessert werden kann. Der von der Datenbank bereitgestellte Mechanismus write_condition ist tatsächlich eine optimistische Sperre. In Java wird die atomare Variablenklasse im Paket java.util.concurrent.atomic mithilfe von CAS implementiert, einer Implementierungsmethode für optimistisches Sperren.
Segmentiertes Schloss
Bei der segmentierten Sperre handelt es sich tatsächlich um ein Sperrendesign und nicht um eine bestimmte Sperre. Bei ConcurrentHashMap besteht die Parallelitätsimplementierung darin, effiziente gleichzeitige Vorgänge in Form segmentierter Sperren zu erreichen.
Der Sperrmechanismus gleichzeitiger Containerklassen basiert auf segmentierten Sperren mit geringerer Granularität. Segmentierte Sperren sind auch eines der wichtigen Mittel zur Verbesserung der Leistung mehrerer gleichzeitiger Programme.
In gleichzeitigen Programmen verringern serielle Vorgänge die Skalierbarkeit und Kontextwechsel verringern auch die Leistung. Diese beiden Probleme werden verursacht, wenn bei der Sperre Konkurrenz auftritt. Bei der Verwendung einer exklusiven Sperre zum Schutz eingeschränkter Ressourcen handelt es sich grundsätzlich um eine serielle Methode – jeweils nur ein Thread kann darauf zugreifen. Die größte Bedrohung für die Skalierbarkeit sind also exklusive Sperren.
Wir haben im Allgemeinen drei Möglichkeiten, den Grad der Sperrkonkurrenz zu verringern: 1. Reduzieren Sie die Sperrhaltezeit. 2. Reduzieren Sie die Häufigkeit von Sperranforderungen. 3. Verwenden Sie exklusive Sperren mit Koordinierungsmechanismen. Diese Mechanismen ermöglichen eine höhere Parallelität.
In einigen Fällen können wir die Sperrenzerlegungstechnik weiter erweitern, um die Sperre in eine Reihe unabhängiger Objekte zu zerlegen, wodurch eine segmentierte Sperre entsteht.
Um es einfach auszudrücken:
Es gibt mehrere Sperren im Container, und jede Sperre wird verwendet, um einen Teil der Daten im Container zu sperren. Wenn dann mehrere Threads auf Daten in verschiedenen Datensegmenten im Container zugreifen, gibt es keinen Sperrenwettbewerb zwischen Threads, was effektiv sein kann Verbessern Sie die Effizienz des gleichzeitigen Zugriffs. Dies ist die von ConcurrentHashMap verwendete Sperrsegmentierungstechnologie. Zuerst werden die Daten zur Speicherung in Segmente unterteilt, und dann wird jedem Datensegment eine Sperre zugewiesen, um auf eines zuzugreifen Auf die Daten anderer Segmente können auch andere Threads zugreifen.
Beispiel: In ConcurrentHashMap wird ein Array mit 16 Sperren verwendet. Jede Sperre schützt 1/16 aller Hash-Buckets, und der N-te Hash-Bucket wird durch die (N mod 16)-te Sperre geschützt. Unter der Annahme, dass ein vernünftiger Hashing-Algorithmus verwendet wird, um die Schlüssel gleichmäßig zu verteilen, kann dies die Sperranforderungen ungefähr auf 1/16 reduzieren. Dank dieser Technologie kann ConcurrentHashMap bis zu 16 gleichzeitige Schreibthreads unterstützen.
Schrägschloss / Leichtgewichtschloss / Schwergewichtschloss
Sperrstatus:
Entsperrter Status
Bias-Sperrzustand
Lightweight-Sperrstatus
Schwergewichts-Sperrstatus
Der Status der Sperre wird durch die Felder des Objektmonitors im Objektkopf angezeigt. Die vier Staaten werden mit dem Wettbewerb allmählich eskalieren, und es ist ein irreversibler Prozess, das heißt, er kann nicht herabgestuft werden. Bei diesen vier Zuständen handelt es sich nicht um Sperren in der Java-Sprache, sondern um Optimierungen, die von der JVM vorgenommen wurden, um die Effizienz der Sperrenerfassung und -freigabe (bei Verwendung von synchronisiert) zu verbessern.
Vorspannungssperre
Voreingenommene Sperre bedeutet, dass immer ein Thread auf einen Teil des Synchronisationscodes zugreift und der Thread dann automatisch die Sperre erhält. Reduzieren Sie die Kosten für die Anschaffung von Schlössern.
Leicht
Eine leichte Sperre bedeutet, dass, wenn die Sperre eine voreingenommene Sperre ist und von einem anderen Thread darauf zugegriffen wird, andere Threads versuchen, die Sperre durch Spin zu erhalten, was die Leistung nicht blockiert.
Schwergewichtsschloss
Eine schwere Sperre bedeutet, dass, wenn es sich bei der Sperre um eine leichte Sperre handelt, die Drehung nicht für immer fortgesetzt wird, wenn die Sperre nicht erfasst wurde erweitert sich zu einem Schwergewichtsschloss. Schwere Sperren blockieren andere Anwendungsthreads und verringern die Leistung.
Spin-Lock
Wir wissen, dass der CAS-Algorithmus eine Implementierungsmethode für optimistisches Sperren ist. Der CAS-Algorithmus beinhaltet auch Spin-Locks, daher werde ich Ihnen hier erklären, was ein Spin-Lock ist.
Ein kurzer Überblick über den CAS-Algorithmus
CAS ist das englische Wort Compare and Swap (Compare and Swap), ein berühmter sperrfreier Algorithmus. Sperrfreie Programmierung bedeutet, Variablen zwischen mehreren Threads ohne Verwendung von Sperren zu synchronisieren, dh Variablen zu synchronisieren, ohne dass Threads blockiert werden. Daher wird dies auch als nicht blockierende Synchronisierung bezeichnet. Der CAS-Algorithmus umfasst drei Operanden
Der Speicherwert V
, der gelesen und geschrieben werden muss Der zu vergleichende Wert A
Der neu zu schreibende Wert B
Beim Aktualisieren einer Variablen wird der der Speicheradresse V entsprechende Wert nur dann in B geändert, wenn der erwartete Wert A der Variablen mit dem tatsächlichen Wert in der Speicheradresse V übereinstimmt. Andernfalls wird keine Operation ausgeführt. Im Allgemeinen handelt es sich um eine Spin-Operation, also ständige Wiederholungsversuche.
Was ist ein Spinlock?
Spin-Sperre (Spinlock): Wenn ein Thread eine Sperre erhält und die Sperre von einem anderen Thread erworben wurde, wartet der Thread in einer Schleife und beurteilt dann weiter, ob die Sperre erfolgreich erworben werden kann, bis die Sperre erworben wird die Schleife.
Es handelt sich um einen Sperrmechanismus, der zum Schutz gemeinsam genutzter Ressourcen vorgeschlagen wird. Tatsächlich ähneln Spin-Sperren Mutex-Sperren. Beide dienen dazu, die sich gegenseitig ausschließende Nutzung einer bestimmten Ressource zu lösen. Unabhängig davon, ob es sich um eine Mutex-Sperre oder eine Spin-Sperre handelt, kann es zu jedem Zeitpunkt höchstens einen Inhaber geben. Mit anderen Worten: Es kann zu jedem Zeitpunkt höchstens eine Ausführungseinheit die Sperre erhalten. Aber die beiden haben leicht unterschiedliche Planungsmechanismen. Wenn bei Mutex-Sperren die Ressource bereits belegt ist, kann der Ressourcenantragsteller nur in den Ruhezustand wechseln. Die Spin-Sperre führt jedoch nicht dazu, dass der Aufrufer in den Ruhezustand versetzt wird. Wenn die Spin-Sperre von einer anderen Ausführungseinheit gehalten wurde, wird der Aufrufer dort weitermachen, um zu sehen, ob der Inhaber der Spin-Sperre die Sperre freigegeben hat. Deshalb hat es seinen Namen bekommen.
Wie implementiert man Spin Lock in Java?
Hier ist ein einfaches Beispiel:
öffentliche Klasse SpinLock {
private AtomicReference
public void lock() {
Thread current = Thread.currentThread();
//CAS
verwenden while (!cas.compareAndSet(null, current)) {
// Nichts tun
}
}
public void unlock() {
Thread current = Thread.currentThread();
cas.compareAndSet(current, null);
}
}
Die lock()-Methode verwendet CAS, wenn der erste Thread A die Sperre erhält und nicht in die while-Schleife eintreten kann. Wenn Thread A die Sperre zu diesem Zeitpunkt nicht aufhebt, wird ein anderer Thread B die Sperre erneut erwerben Da der CAS zu diesem Zeitpunkt nicht erfüllt ist, tritt er in eine While-Schleife ein und beurteilt kontinuierlich, ob der CAS erfüllt ist, bis der A-Thread die Entsperrmethode aufruft, um die Sperre aufzuheben.
Probleme mit Spinlocks
1. Wenn ein Thread die Sperre zu lange hält, führt dies dazu, dass andere Threads, die darauf warten, die Sperre zu erhalten, in eine Schleife eintreten und CPU verbrauchen. Eine unsachgemäße Verwendung kann zu einer extrem hohen CPU-Auslastung führen. 2. Die oben in Java implementierte Spin-Sperre ist nicht fair, das heißt, sie kann den Thread mit der längsten Wartezeit nicht erfüllen, um die Sperre zuerst zu erhalten. Unfaire Sperren führen zu „Thread-Starvation“-Problemen.
Vorteile von Spin Lock
1. Die Spin-Sperre führt nicht dazu, dass der Thread-Status wechselt, und er befindet sich immer im Benutzerstatus, dh der Thread ist immer aktiv. Dadurch wird der Thread nicht in den Blockierungsstatus versetzt, wodurch unnötige Kontextwechsel vermieden werden , und die Ausführungsgeschwindigkeit wird hoch sein. 2. Nicht-Spin Wenn die Sperre nicht erworben werden kann, wechselt die Sperre in den Blockierungszustand und wechselt in den Kernel-Zustand. was einen Thread-Kontextwechsel erfordert. (Nachdem der Thread blockiert wurde, wechselt er in den Kernel-Planungsstatus (Linux). Dadurch wechselt das System zwischen Benutzermodus und Kernelmodus hin und her, was die Leistung der Sperre erheblich beeinträchtigt.)
Wiedereintrittsfähige Spinsperre und nicht wiedereintrittsfähige Spinsperre
Eine sorgfältige Analyse des Codes am Anfang des Artikels zeigt, dass er keine Wiedereintrittsfunktion unterstützt. Das heißt, wenn ein Thread die Sperre zum ersten Mal erworben hat, erhält er die Sperre erneut, bevor die Sperre aufgehoben wird zum zweiten Mal erfolgreich. Da CAS nicht erfüllt ist, tritt die zweite Erfassung in eine While-Schleife ein und wartet. Wenn es sich um eine Wiedereintrittssperre handelt, sollte sie beim zweiten Mal erfolgreich erfasst werden.
Selbst wenn es zum zweiten Mal erfolgreich erworben werden kann, wird beim ersten Aufheben der Sperre auch die zum zweiten Mal erworbene Sperre aufgehoben, was unangemessen ist.
Um eine Wiedereintrittssperre zu implementieren, müssen wir einen Zähler einführen, der die Anzahl der Threads aufzeichnet, die die Sperre erhalten.
öffentliche Klasse ReentrantSpinLock {
private AtomicReference
private int count;
public void lock() {
Thread current = Thread.currentThread();
if (current == cas.get()) { // Wenn der aktuelle Thread die Sperre erhalten hat, erhöhen Sie die Anzahl der Threads um eins und geben Sie dann
zurück count++;
zurück;
}
// Wenn die Sperre nicht erworben wird, durchlaufen Sie CAS
while (!cas.compareAndSet(null, current)) {
// Nichts tun
}
}
public void unlock() {
Thread cur = Thread.currentThread();
if (cur == cas.get()) {
if (count > 0) {//Wenn größer als 0, bedeutet dies, dass der aktuelle Thread die Sperre mehrmals erworben hat und die Freigabe der Sperre durch Dekrementieren der Anzahl um eins simuliert wird
Zählen--;
} else {// Wenn count==0, kann die Sperre aufgehoben werden, wodurch sichergestellt wird, dass die Häufigkeit, mit der die Sperre erworben wird, mit der Häufigkeit, mit der die Sperre aufgehoben wird, übereinstimmt.
cas.compareAndSet(cur, null);
}
}
}
}
Spin-Lock und Mutex-Lock
Sowohl Spin-Sperren als auch Mutex-Sperren sind Mechanismen zum Schutz der gemeinsamen Nutzung von Ressourcen.
Unabhängig davon, ob es sich um ein Spin-Lock oder ein Mutex-Lock handelt, kann es zu jedem Zeitpunkt höchstens einen Halter geben.
Wenn der Thread, der die Mutex-Sperre erwirbt, bereits belegt ist, wechselt der Thread in den Ruhezustand; der Thread, der die Spin-Sperre erwirbt, wird nicht schlafen, sondern in einer Schleife warten, bis die Sperre aufgehoben wird.
Das obige ist der detaillierte Inhalt vonWas sind die verschiedenen Sperren in Java?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!