Synchronisation gegen Sperre


182

java.util.concurrentDie API stellt eine Klasse namens as bereit Lock, die das Steuerelement grundsätzlich serialisiert, um auf die kritische Ressource zuzugreifen. Es gibt Methoden wie park()und unpark().

Wir können ähnliche Dinge tun, wenn wir synchronizedSchlüsselwörter wait()und notify() notifyAll()Methoden verwenden können.

Ich frage mich, welches davon in der Praxis besser ist und warum?


Antworten:


178

Wenn Sie einfach ein Objekt sperren, würde ich es vorziehen, es zu verwenden synchronized

Beispiel:

Lock.acquire();
doSomethingNifty(); // Throws a NPE!
Lock.release(); // Oh noes, we never release the lock!

Sie müssen explizit try{} finally{}überall tun .

Während mit synchronisiert, ist es super klar und unmöglich, falsch zu liegen:

synchronized(myObject) {
    doSomethingNifty();
}

Das heißt, Locks kann für kompliziertere Dinge nützlicher sein, bei denen Sie nicht auf so saubere Weise erwerben und freigeben können. Ich würde es ehrlich gesagt vorziehen, Bare Locks überhaupt nicht zu verwenden und mich einfach für eine ausgefeiltere Parallelitätskontrolle wie a CyclicBarrieroder a zu entscheidenLinkedBlockingQueue , wenn sie Ihren Anforderungen entspricht.

Ich hatte noch nie einen Grund zu benutzen wait()oder notify()aber es kann einige gute geben.


1
Was ist der Unterschied zwischen Warten / Benachrichtigen und Parken / Parken von LockSupport? docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/…
Pacerier

6
Zuerst machte das Beispiel mit Sperren Sinn, aber dann wurde mir klar, dass, wenn Sie einen Versuch verwenden, dieses Problem endgültig zu blockieren, vermieden werden würde, wenn Sperren nicht freigegeben werden
William Reed

Ahh ... Einer dieser Momente, um das RAII-Modell in C ++ zu schätzen. std::lock_guard
WhiZTiM

67

Ich frage mich, welches davon in der Praxis besser ist und warum?

Ich habe festgestellt, dass Lockund Condition(und andere neue concurrentKlassen) nur mehr Werkzeuge für die Toolbox sind. Ich konnte fast alles, was ich brauchte, mit meinem alten Klauenhammer (dem synchronizedSchlüsselwort) tun , aber es war in manchen Situationen umständlich, ihn zu verwenden. Einige dieser unangenehmen Situationen wurden viel einfacher, als ich meinem Werkzeugkasten weitere Werkzeuge hinzufügte: einen Gummihammer, einen Kugelhammer, einen Stemmeisen und einige Nagelstempel. jedoch , mein alter Klauenhammer sieht immer noch seinen Anteil an der Nutzung.

Ich denke nicht, dass einer wirklich "besser" ist als der andere, sondern dass jeder besser für verschiedene Probleme geeignet ist. Kurz gesagt, das einfache Modell und die bereichsorientierte Natur von synchronizedhelfen mir, mich vor Fehlern in meinem Code zu schützen, aber dieselben Vorteile sind manchmal Hindernisse in komplexeren Szenarien. Es sind diese komplexeren Szenarien, für deren Behebung das gleichzeitige Paket erstellt wurde. Die Verwendung dieser übergeordneten Konstrukte erfordert jedoch eine explizitere und sorgfältigere Verwaltung des Codes.

===

Ich denke, dass JavaDoc die Unterscheidung zwischen Lockund gut beschreibt synchronized(der Schwerpunkt liegt bei mir):

Sperrimplementierungen bieten umfangreichere Sperrvorgänge, als sie mit synchronisierten Methoden und Anweisungen erzielt werden können. Sie ermöglichen eine flexiblere Strukturierung , haben möglicherweise sehr unterschiedliche Eigenschaften und unterstützen möglicherweise mehrere zugeordnete Bedingungsobjekte .

...

Die Verwendung von synchronisierten Methoden oder Anweisungen ermöglicht den Zugriff auf die implizite Monitorverriegelung mit jedem Objekt zugeordnet ist , aber alle Kräfte Lock - Acquisition und Freigabe in einem block strukturiert auftreten : wenn mehrere Schlösser sind erworben sie in umgekehrter Reihenfolge gelöst werden muss , und Alle Sperren müssen in demselben lexikalischen Bereich freigegeben werden, in dem sie erworben wurden .

Während der Scoping-Mechanismus für synchronisierte Methoden und Anweisungen das Programmieren mit Monitorsperren erheblich vereinfacht und dazu beiträgt , viele häufige Programmierfehler bei Sperren zu vermeiden, müssen Sie gelegentlich flexibler mit Sperren arbeiten. Zum Beispiel erfordern * * einige Algorithmen * zum Durchlaufen von Datenstrukturen, auf die gleichzeitig zugegriffen wird , die Verwendung von "Hand-over-Hand" oder "Kettenverriegelung" : Sie erwerben die Sperre von Knoten A, dann Knoten B, geben dann A frei und erwerben C, Lassen Sie dann B los und erwerben Sie D und so weiter. Implementationen der Lock - Schnittstelle , die Verwendung solcher Techniken ermöglichen durch eine Sperre ermöglicht in verschiedenen Bereichen erworben und freigegeben werden , undSo können mehrere Sperren in beliebiger Reihenfolge erworben und freigegeben werden .

Mit dieser erhöhten Flexibilität geht zusätzliche Verantwortung einher . Das Fehlen blockblockierter Sperren beseitigt die automatische Freigabe von Sperren , die bei synchronisierten Methoden und Anweisungen auftritt. In den meisten Fällen sollte die folgende Redewendung verwendet werden:

...

Beim Verriegeln und Entriegeln kommen in verschiedenen Bereichen , Sorgfalt getroffen werden müssen sicherstellen , dass der gesamte Code, der ausgeführt wird , während die Sperre gehalten wird durch geschützt try-finally oder versuchen Beifang zu gewährleisten , dass die Sperre aufgehoben wird , wenn notwendig.

Lock - Implementierungen bieten zusätzliche Funktionalität über die Verwendung von synchronisierten Methoden und Aussagen durch die Bereitstellung eines nicht-blockierenden Versuch zu erwerben eine Sperre (TryLock ()), ein Versuch, die Sperre zu erwerben , die unterbrochen werden kann (lockInterruptibly () und ein Versuch, acquire die Sperre, die eine Zeitüberschreitung verursachen kann (tryLock (long, TimeUnit)).

...


23

Sie können alles , was der Dienstprogramme in erreichen java.util.concurrent wie bei dem Low-Level - Primitive tun synchronized, volatileoder Warte / benachrichtigen

Parallelität ist jedoch schwierig, und die meisten Leute verstehen zumindest einige Teile falsch, wodurch ihr Code entweder falsch oder ineffizient (oder beides) wird.

Die gleichzeitige API bietet einen übergeordneten Ansatz, der einfacher (und damit sicherer) zu verwenden ist. Kurz gesagt, Sie sollten nicht synchronized, volatile, wait, notifymehr direkt verwenden müssen.

Die Lock - Klasse selbst auf der unteren Ebene Seite dieser Toolbox ist, können Sie nicht einmal , dass entweder direkt verwenden müssen (Sie verwenden können , Queuesund Semaphore und Sachen, etc, die meiste Zeit).


2
Wird einfaches altes Warten / Benachrichtigen als untergeordnetes Grundelement angesehen als java.util.concurrent.locks.LockSupports Park / Unpark, oder ist es umgekehrt?
Pacerier

@Pacerier: Ich betrachte beides als Low-Level (dh etwas, das ein Anwendungsprogrammierer vermeiden möchte, direkt zu verwenden), aber sicherlich sind die untergeordneten Teile von java.util.concurrency (wie das Locks-Paket) darauf aufgebaut der nativen JVM-Grundelemente warten / benachrichtigen (die noch niedriger sind).
Thilo

2
Nein , ich meine aus dem 3: Thread.sleep / Interrupt, Object.wait / notify, LockSupport.park / entparken, was das ist den meisten primitiv?
Pacerier

2
@Thilo Ich bin mir nicht sicher, wie Sie Ihre Aussage unterstützen, die java.util.concurrent[im Allgemeinen] einfacher ist als die Sprachfunktionen ( synchronizedusw.). Wenn Sie verwenden java.util.concurrent, müssen Sie es sich zur Gewohnheit machen, lock.lock(); try { ... } finally { lock.unlock() }vor dem Schreiben von Code zu vervollständigen , während Sie mit a synchronizedvon Anfang an in Ordnung sind. Allein auf dieser Basis würde ich sagen, synchronizedist einfacher (vorausgesetzt, Sie wollen sein Verhalten) als java.util.concurrent.locks.Lock. Par 4
Iwan Aucamp

1
Denken Sie nicht, dass Sie das Verhalten der AtomicXXX-Klassen nur mit den Parallelitätsprimitiven exakt replizieren können, da sie auf nativen CAS-Aufrufen beruhen, die vor java.util.concurrent nicht verfügbar waren.
Duncan Armstrong

15

Es gibt 4 Hauptfaktoren, warum Sie synchronizedoder verwenden möchten java.util.concurrent.Lock.

Hinweis: Synchronisiertes Sperren ist das, was ich meine, wenn ich intrinsisches Sperren sage.

  1. Als Java 5 mit ReentrantLocks herauskam, zeigten sie einen beachtlichen Durchsatzunterschied gegenüber dem intrinsischen Sperren. Wenn Sie nach einem schnelleren Verriegelungsmechanismus suchen und 1.5 ausführen, sollten Sie jucReentrantLock in Betracht ziehen. Die intrinsische Sperrung von Java 6 ist jetzt vergleichbar.

  2. jucLock verfügt über verschiedene Sperrmechanismen. Sperre unterbrechbar - Versuch zu sperren, bis der Sperrfaden unterbrochen ist; zeitgesteuerte Sperre - Versuchen Sie, für eine bestimmte Zeit zu sperren und aufzugeben, wenn Sie keinen Erfolg haben. tryLock - Versuch zu sperren, wenn ein anderer Thread die Sperre hält, gib auf. Dies alles ist bis auf das einfache Schloss enthalten. Die intrinsische Verriegelung bietet nur eine einfache Verriegelung

  3. Stil. Wenn sowohl 1 als auch 2 nicht in Kategorien von dem fallen, was Sie mit den meisten Menschen, einschließlich mir, zu tun haben, wäre die intrinsische Sperrsematik leichter zu lesen und weniger ausführlich als die jucLock-Sperre.
  4. Mehrere Bedingungen. Ein Objekt, das Sie sperren, kann nur benachrichtigt und auf einen einzelnen Fall gewartet werden. Die newCondition-Methode von Lock ermöglicht es einem einzelnen Lock, mehrere Gründe zu haben, zu warten oder zu signalisieren. Ich habe diese Funktionalität in der Praxis noch nicht benötigt, aber sie ist eine nette Funktion für diejenigen, die sie benötigen.

Ich mochte die Details zu Ihrem Kommentar. Ich würde noch einen Aufzählungspunkt hinzufügen - das ReadWriteLock bietet nützliches Verhalten, wenn Sie mit mehreren Threads arbeiten, von denen nur einige in das Objekt schreiben müssen. Mehrere Threads können das Objekt gleichzeitig lesen und werden nur blockiert, wenn bereits ein anderer Thread darauf schreibt.
Sam Goldberg

5

Ich möchte noch einige Dinge zur Antwort von Bert F hinzufügen .

Locksunterstützen verschiedene Methoden zur feinkörnigeren Sperrsteuerung, die aussagekräftiger sind als implizite Monitore ( synchronizedSperren)

Eine Sperre bietet exklusiven Zugriff auf eine gemeinsam genutzte Ressource: Es kann jeweils nur ein Thread die Sperre erwerben, und für den gesamten Zugriff auf die gemeinsam genutzte Ressource muss die Sperre zuerst erworben werden. Einige Sperren ermöglichen jedoch möglicherweise den gleichzeitigen Zugriff auf eine gemeinsam genutzte Ressource, z. B. die Lesesperre eines ReadWriteLock.

Vorteile der Sperre über Synchronisation von Dokumentation Seite

  1. Die Verwendung synchronisierter Methoden oder Anweisungen ermöglicht den Zugriff auf die implizite Monitorsperre, die jedem Objekt zugeordnet ist, erzwingt jedoch die blockstrukturierte Erfassung und Freigabe aller Sperren

  2. Sperrenimplementierungen bieten zusätzliche Funktionen gegenüber der Verwendung synchronisierter Methoden und Anweisungen, indem sie einen nicht blockierenden Versuch zum Erfassen von a lock (tryLock()), einen Versuch zum Erfassen der Sperre, die unterbrochen werden kann ( lockInterruptibly()und einen Versuch zum Erfassen der Sperre, die dies kann) bereitstellen timeout (tryLock(long, TimeUnit)).

  3. Eine Lock-Klasse kann auch ein Verhalten und eine Semantik bereitstellen, die sich erheblich von denen der impliziten Monitorsperre unterscheiden, z. B. garantierte Reihenfolge, nicht wiedereintretende Verwendung oder Deadlock-Erkennung

ReentrantLock : Ermöglicht nach meinem Verständnis in einfachen Worten, ReentrantLockdass ein Objekt von einem kritischen Abschnitt in einen anderen kritischen Abschnitt zurückkehrt. Da Sie bereits über eine Sperre verfügen, um einen kritischen Abschnitt einzugeben, können Sie mithilfe der aktuellen Sperre einen anderen kritischen Abschnitt für dasselbe Objekt erstellen.

ReentrantLockHauptmerkmale gemäß diesem Artikel

  1. Fähigkeit, unterbrechungsfrei zu sperren.
  2. Zeitüberschreitung beim Warten auf die Sperre.
  3. Macht, faires Schloss zu schaffen.
  4. API, um eine Liste der wartenden Threads für die Sperre abzurufen.
  5. Flexibilität, um zu versuchen, zu sperren, ohne zu blockieren.

Sie können ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLockdamit die Kontrolle über das granulare Sperren bei Lese- und Schreibvorgängen weiter erlangen.

Abgesehen von diesen drei ReentrantLocks bietet Java 8 eine weitere Sperre

StampedLock:

Java 8 wird mit einer neuen Art von Sperre namens StampedLock ausgeliefert, die auch Lese- und Schreibsperren wie im obigen Beispiel unterstützt. Im Gegensatz zu ReadWriteLock geben die Sperrmethoden eines StampedLock einen Stempel zurück, der durch einen langen Wert dargestellt wird.

Mit diesen Stempeln können Sie entweder eine Sperre aufheben oder prüfen, ob die Sperre noch gültig ist. Zusätzlich gestempelte Sperren unterstützen einen anderen Sperrmodus, der als optimistisches Sperren bezeichnet wird.

Schauen Sie sich diesen Artikel über die Verwendung verschiedener Arten von ReentrantLockund StampedLockSchlössern an.


4

Der Hauptunterschied ist die Fairness. Mit anderen Worten, werden Anfragen per FIFO bearbeitet oder kann es zu einem Barging kommen? Die Synchronisation auf Methodenebene gewährleistet eine faire oder FIFO-Zuweisung der Sperre. Verwenden von

synchronized(foo) {
}

oder

lock.acquire(); .....lock.release();

garantiert keine Fairness.

Wenn Sie viele Konflikte um die Sperre haben, können Sie leicht auf Barging stoßen, wenn neuere Anforderungen die Sperre erhalten und ältere Anforderungen stecken bleiben. Ich habe Fälle gesehen, in denen 200 Threads in kurzer Zeit für eine Sperre eintreffen und der zweite, der ankommt, zuletzt verarbeitet wurde. Dies ist für einige Anwendungen in Ordnung, für andere jedoch tödlich.

Eine ausführliche Beschreibung dieses Themas finden Sie in Brian Goetz 'Buch "Java Concurrency In Practice", Abschnitt 13.3.


5
"Die Synchronisation auf Methodenebene gewährleistet eine faire oder FIFO-Zuweisung der Sperre." => Wirklich? Wollen Sie damit sagen, dass sich eine synchronisierte Methode in Bezug auf Fairness anders verhält als das Umschließen des Methodeninhalts in einen synchronisierten {} Block? Ich würde es nicht glauben, oder habe ich diesen Satz falsch verstanden ...?
Weiresr

Ja, obwohl überraschend und kontraintuitiv, ist das richtig. Goetz 'Buch ist die beste Erklärung.
Brian Tarbox

Wenn Sie sich den von @BrianTarbox bereitgestellten Code ansehen, verwendet der synchronisierte Block ein anderes Objekt als "this" zum Sperren. Theoretisch gibt es keinen Unterschied zwischen einer synchronisierten Methode und dem Platzieren des gesamten Körpers dieser Methode in einem synchronisierten Block, solange der Block "dies" als Sperre verwendet.
Xburgos

Die Antwort sollte so bearbeitet werden, dass sie ein Zitat enthält, und es sollte klargestellt werden, dass "Zusicherung" hier "statistische Garantie" und nicht deterministisch ist.
Nathan Hughes

Entschuldigung, ich habe gerade festgestellt, dass ich diese Antwort vor einigen Tagen fälschlicherweise abgelehnt habe (ungeschicktes Klicken). Leider lässt mich SO derzeit nicht zurücksetzen. Hoffe ich kann es später beheben.
Mikko Östlund

3

Brian Goetz 'Buch "Java Concurrency In Practice", Abschnitt 13.3: "... Wie das Standard-ReentrantLock bietet das intrinsische Sperren keine deterministischen Fairness-Garantien, aber die statistischen Fairness-Garantien der meisten Locking-Implementierungen sind für fast alle Situationen gut genug ..."


1

Lock erleichtert Programmierern das Leben. Hier sind einige Situationen, die mit Lock einfacher erreicht werden können.

  1. Sperren Sie eine Methode und lösen Sie die Sperre in einer anderen Methode.
  2. Sie haben zwei Threads, die an zwei verschiedenen Codeteilen arbeiten. Der erste Thread ist jedoch von dem zweiten Thread abhängig, um einen bestimmten Codeteil zu vervollständigen, bevor er fortgesetzt wird (während einige andere Threads gleichzeitig arbeiten). Eine gemeinsame Sperre kann dieses Problem ganz einfach lösen.
  3. Monitore implementieren. Zum Beispiel eine einfache Warteschlange, in der die Put- und Get-Methoden von vielen verschiedenen Threads ausgeführt werden. Möchten Sie jedoch weder die gleichen Methoden übereinander überlappen, noch können sich die Put- und Get-Methoden überschneiden. In einem solchen Fall erleichtert ein privates Schloss das Leben sehr.

Währenddessen werden die Sperre und die Bedingungen auf dem synchronisierten aufgebaut. Damit können Sie sicher das gleiche Ziel erreichen. Dies könnte jedoch Ihr Leben erschweren und Sie von der Lösung des eigentlichen Problems abweichen.


1

Hauptunterschied zwischen Sperre und Synchronisation:

  • Mit Schlössern können Sie die Schlösser in beliebiger Reihenfolge freigeben und erwerben.
  • Mit synchronisiert können Sie die Sperren nur in der Reihenfolge aufheben, in der sie erworben wurden.

0

Das Sperren und Synchronisieren des Blocks dient demselben Zweck, hängt jedoch von der Verwendung ab. Betrachten Sie den folgenden Teil

void randomFunction(){
.
.
.
synchronize(this){
//do some functionality
}

.
.
.
synchronize(this)
{
// do some functionality
}


} // end of randomFunction

Wenn im obigen Fall ein Thread in den Synchronisationsblock eintritt, wird auch der andere Block gesperrt. Wenn mehrere solcher Synchronisationsblöcke für dasselbe Objekt vorhanden sind, sind alle Blöcke gesperrt. In solchen Situationen kann java.util.concurrent.Lock verwendet werden, um ein unerwünschtes Sperren von Blöcken zu verhindern

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.