Warum ein ReentrantLock verwenden, wenn man synchronisiert (dies) verwenden kann?


317

Ich versuche zu verstehen, was die Sperre in der Parallelität so wichtig macht, wenn man sie verwenden kann synchronized (this). Im folgenden Dummy-Code kann ich Folgendes tun:

  1. synchronisierte die gesamte Methode oder synchronisierte den gefährdeten Bereich ( synchronized(this){...})
  2. ODER sperren Sie den anfälligen Codebereich mit einem ReentrantLock.

Code:

    private final ReentrantLock lock = new ReentrantLock(); 
    private static List<Integer> ints;

    public Integer getResult(String name) { 
        .
        .
        .
        lock.lock();
        try {
            if (ints.size()==3) {
                ints=null;
                return -9;
            }   

            for (int x=0; x<ints.size(); x++) {
                System.out.println("["+name+"] "+x+"/"+ints.size()+". values >>>>"+ints.get(x));
            }

        } finally {
            lock.unlock();
        } 
        return random;
}

1
Übrigens sind alle Java-Eigensperren von Natur aus wiedereintrittsfähig.
Aniket Thakur

@pongapundit synchronized(this){synchronized(this){//some code}}wird also keine Deadlock verursachen. Für die intrinsische Sperre, wenn sie einen Monitor für eine Ressource erhalten und ihn erneut benötigen, können sie ihn ohne Dead Lock erhalten.
Aniket Thakur

Antworten:


474

Ein ReentrantLock ist im Gegensatz zu Konstrukten unstrukturiertsynchronized - dh Sie müssen keine Blockstruktur zum Sperren verwenden und können sogar eine Methode zwischen Methoden sperren. Ein Beispiel:

private ReentrantLock lock;

public void foo() {
  ...
  lock.lock();
  ...
}

public void bar() {
  ...
  lock.unlock();
  ...
}

Ein solcher Fluss kann nicht über einen einzelnen Monitor in einem synchronizedKonstrukt dargestellt werden.


Abgesehen davon ReentrantLockunterstützt Lock Poll Polling und unterbrechbare Lock Waits, die Timeout unterstützen . ReentrantLockUnterstützt auch konfigurierbare Fairness- Richtlinien , die eine flexiblere Thread-Planung ermöglichen.

Der Konstruktor für diese Klasse akzeptiert einen optionalen Fairness- Parameter. Wenn die festgelegte Methode die Fairness-Einstellung nicht berücksichtigt. Es ist erfolgreich, wenn die Sperre verfügbar ist, auch wenn andere Threads warten.true aktiviert ist, wird durch Sperren der Zugriff auf den am längsten wartenden Thread bevorzugt. Andernfalls garantiert dieses Schloss keine bestimmte Zugriffsreihenfolge. Programme mit fairen Sperren, auf die viele Threads zugreifen, weisen möglicherweise einen geringeren Gesamtdurchsatz auf (dh sind langsamer; oft viel langsamer) als Programme mit der Standardeinstellung, weisen jedoch zeitlich geringere Abweichungen auf, um Sperren zu erhalten und einen Mangel an Hunger zu gewährleisten. Beachten Sie jedoch, dass die Fairness der Sperren keine Fairness der Thread-Planung garantiert. Somit kann einer von vielen Threads, die eine faire Sperre verwenden, diese mehrmals hintereinander erhalten, während andere aktive Threads nicht fortschreiten und die Sperre derzeit nicht halten. Beachten Sie auch, dass die ohne ZeitangabetryLock


ReentrantLock kann auch skalierbarer sein und bei höheren Konflikten eine viel bessere Leistung erbringen. Mehr dazu lesen Sie hier .

Diese Behauptung wurde jedoch bestritten; siehe folgenden Kommentar:

Beim Test der Wiedereintrittssperre wird jedes Mal eine neue Sperre erstellt, sodass keine ausschließliche Sperre erfolgt und die resultierenden Daten ungültig sind. Außerdem bietet der IBM Link keinen Quellcode für den zugrunde liegenden Benchmark, sodass nicht charakterisiert werden kann, ob der Test überhaupt korrekt durchgeführt wurde.


Wann sollten Sie ReentrantLocks verwenden? Laut diesem DeveloperWorks-Artikel ...

Die Antwort ist ziemlich einfach: Verwenden Sie sie, wenn Sie tatsächlich etwas benötigen, synchronizeddas nicht bereitgestellt wird, z. B. zeitgesteuerte Wartezeiten, unterbrechbare Wartezeiten, nicht blockstrukturierte Sperren, mehrere Bedingungsvariablen oder Sperrenabfragen. ReentrantLockhat auch Skalierbarkeitsvorteile, und Sie sollten es verwenden, wenn Sie tatsächlich eine Situation haben, die hohe Konflikte aufweist. Denken Sie jedoch daran, dass die überwiegende Mehrheit der synchronizedBlöcke kaum Konflikte aufweist, geschweige denn hohe Konflikte. Ich würde empfehlen, mit Synchronisation zu entwickeln, bis sich die Synchronisation als unzureichend erwiesen hat, anstatt einfach davon auszugehen, dass "die Leistung besser ist", wenn Sie sie verwendenReentrantLock. Denken Sie daran, dies sind erweiterte Tools für fortgeschrittene Benutzer. (Und wirklich fortgeschrittene Benutzer bevorzugen in der Regel die einfachsten Tools, die sie finden können, bis sie überzeugt sind, dass die einfachen Tools unzureichend sind.) Machen Sie es wie immer zuerst richtig und machen Sie sich dann Gedanken darüber, ob Sie es schneller machen müssen oder nicht.


26
Der Link "bekanntermaßen skalierbarer" zu lycog.com sollte entfernt werden. Beim Test der Wiedereintrittssperre wird jedes Mal eine neue Sperre erstellt, sodass keine exklusive Sperre erfolgt und die resultierenden Daten ungültig sind. Außerdem bietet der IBM Link keinen Quellcode für den zugrunde liegenden Benchmark, sodass nicht charakterisiert werden kann, ob der Test überhaupt korrekt durchgeführt wurde. Persönlich würde ich nur die ganze Zeile über die Skalierbarkeit entfernen, da der gesamte Anspruch im Wesentlichen nicht unterstützt wird.
Dev

2
Ich habe den Beitrag im Lichte Ihrer Antwort geändert.
Oldrinb

6
Wenn Ihnen die Leistung ein großes Anliegen ist, vergessen Sie nicht, nach einem Weg zu suchen, bei dem Sie KEINE Synchronisation benötigen.
Mcoolive

2
Die Performance-Sache macht für mich überhaupt keinen Sinn. Wenn die Reantrant-Sperre eine bessere Leistung erbringen würde, warum würde die Synchronisierung dann nicht einfach auf die gleiche Weise implementiert wie eine Reantrant-Sperre intern?
Bis

2
@ user2761895 Der ReentrantLockPseudoRandomCode im Lycog-Link verwendet brandneue, unkontrollierte Sperren für jeden Aufruf von setSeedundnext
oldrinb

14

ReentrantReadWriteLock ist ein spezialisiertes Schloss während synchronized(this) es sich um ein Allzweckschloss handelt. Sie sind ähnlich, aber nicht ganz gleich.

Sie haben Recht damit, dass Sie synchronized(this)stattdessen verwenden könnten, ReentrantReadWriteLockaber das Gegenteil ist nicht immer der Fall.

Wenn Sie besser verstehen möchten, was das ReentrantReadWriteLockBesondere ausmacht, lesen Sie einige Informationen zur Synchronisierung von Produzenten und Verbrauchern.

Im Allgemeinen können Sie sich daran erinnern, dass die Synchronisierung mit ganzen Methoden und die allgemeine Synchronisierung (unter Verwendung des synchronizedSchlüsselworts) in den meisten Anwendungen verwendet werden können, ohne zu viel über die Semantik der Synchronisierung nachzudenken. Wenn Sie jedoch die Leistung aus Ihrem Code herausholen müssen, müssen Sie dies möglicherweise tun Erforschen Sie andere feinkörnigere oder speziellere Synchronisationsmechanismen.

Übrigens kann die Verwendung synchronized(this)- und im Allgemeinen das Sperren mithilfe einer öffentlichen Klasseninstanz - problematisch sein, da dadurch Ihr Code für potenzielle Deadlocks geöffnet wird, da jemand anderes, der nicht wissentlich versucht, an einer anderen Stelle im Programm gegen Ihr Objekt zu sperren.


Um mögliche Deadlocks zu verhindern, weil jemand anderes, der nicht wissentlich versucht, gegen Ihr Objekt an einer anderen Stelle im Programm zu sperren, eine private Objektinstanz als Synchronisationsmonitor wie folgt verwenden: public class MyLock { private final Object protectedLongLockingMonitor = new Object(); private long protectedLong = 0L; public void incrementProtectedLong() { synchronized(protectedLongLockingMonitor) { protectedLong++; } } }
Sushicutta

9

Von der Oracle-Dokumentationsseite über ReentrantLock :

Eine reentrante Sperre für den gegenseitigen Ausschluss mit demselben grundlegenden Verhalten und derselben Semantik wie die implizite Monitorsperre, auf die mit synchronisierten Methoden und Anweisungen zugegriffen wird, jedoch mit erweiterten Funktionen.

  1. Ein ReentrantLock gehört dem Thread, der zuletzt erfolgreich gesperrt , aber noch nicht entsperrt wurde. Ein Thread, der die Sperre aufruft, kehrt zurück und erhält die Sperre erfolgreich, wenn die Sperre keinem anderen Thread gehört. Die Methode wird sofort zurückgegeben, wenn der aktuelle Thread die Sperre bereits besitzt.

  2. Der Konstruktor für diese Klasse akzeptiert einen optionalen Fairness- Parameter. Wenn true festgelegt ist, wird durch Sperren der Zugriff auf den am längsten wartenden Thread bevorzugt . Andernfalls garantiert dieses Schloss keine bestimmte Zugriffsreihenfolge.

ReentrantLock-Hauptfunktionen 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.

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

Schauen Sie sich diesen Artikel von Benjamen über die Verwendung verschiedener Arten von ReentrantLocks an


2

Sie können wiedereintretende Sperren mit einer Fairness-Richtlinie oder einem Timeout verwenden, um einen Thread-Mangel zu vermeiden. Sie können eine Thread-Fairness-Richtlinie anwenden. Dadurch wird vermieden, dass ein Thread ewig darauf wartet, auf Ihre Ressourcen zuzugreifen.

private final ReentrantLock lock = new ReentrantLock(true);
//the param true turns on the fairness policy. 

Die "Fairness Policy" wählt den nächsten ausführbaren Thread aus, der ausgeführt werden soll. Es basiert auf der Priorität, der Zeit seit dem letzten Lauf, bla bla

Außerdem kann Synchronize auf unbestimmte Zeit blockieren, wenn es dem Block nicht entkommen kann. Beim Wiedereintritt kann eine Zeitüberschreitung eingestellt werden.


1

Synchronisierte Sperren bieten keinen Mechanismus für Warteschlangen, in dem nach der Ausführung eines Threads ein parallel laufender Thread die Sperre erhalten kann. Aufgrund dessen erhält der Thread, der sich im System befindet und über einen längeren Zeitraum ausgeführt wird, nie die Chance, auf die gemeinsam genutzte Ressource zuzugreifen, was zu Hunger führt.

Wiedereintrittssperren sind sehr flexibel und haben eine Fairness-Richtlinie, nach der wir sicherstellen können, dass der länger wartende Thread die Chance erhält, auf die gemeinsam genutzte Ressource zuzugreifen, wenn ein Thread länger wartet und nach Abschluss des aktuell ausgeführten Threads abnimmt den Durchsatz des Systems und macht es zeitaufwändiger.


1

Nehmen wir an, dieser Code wird in einem Thread ausgeführt:

private static ReentrantLock lock = new ReentrantLock();

void accessResource() {
    lock.lock();
    if( checkSomeCondition() ) {
        accessResource();
    }
    lock.unlock();
}

Da der Thread die Sperre besitzt, können mehrere Aufrufe gesperrt werden (), sodass die Sperre erneut aktiviert wird. Dies kann mit einem Referenzzähler erreicht werden, sodass keine erneute Sperre erforderlich ist.


0

Eine Sache, die Sie beachten sollten, ist:

Der Name ' ReentrantLock ' gibt eine falsche Meldung über andere Sperrmechanismen aus, dass sie nicht wieder eintreten. Das ist nicht wahr. Die über 'synchronisiert' erworbene Sperre ist auch in Java wieder verfügbar.

Der Hauptunterschied besteht darin, dass 'synchronisiert' eine intrinsische Sperre verwendet (eine, die jedes Objekt hat), während die Sperr-API dies nicht tut.


0

Ich denke, die wait / notify / notifyAll-Methoden gehören nicht zur Object-Klasse, da sie alle Objekte mit Methoden verschmutzen, die selten verwendet werden. Sie sind in einer speziellen Lock-Klasse viel sinnvoller. Unter diesem Gesichtspunkt ist es vielleicht besser, ein Tool zu verwenden, das explizit für den jeweiligen Job entwickelt wurde - z. B. ReentrantLock.

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.