Perché usare un ReentrantLock se si può usare sincronizzato (questo)?


317

Sto cercando di capire cosa rende il blocco in concorrenza così importante se si può usare synchronized (this). Nel codice fittizio di seguito, posso fare:

  1. sincronizzato l'intero metodo o sincronizzare l'area vulnerabile ( synchronized(this){...})
  2. O bloccare l'area di codice vulnerabile con un ReentrantLock.

Codice:

    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
A proposito tutte le serrature intrinseche Java sono rientranti per natura.
Aniket Thakur,

@pongapundit quindi synchronized(this){synchronized(this){//some code}}non causerà dead lock. Per il blocco intrinseco se ottengono il monitoraggio su una risorsa e se lo desiderano di nuovo possono ottenerlo senza un blocco morto.
Aniket Thakur,

Risposte:


474

Un ReentrantLock non è strutturato , a differenza dei synchronizedcostrutti, ovvero non è necessario utilizzare una struttura a blocchi per bloccare e può persino tenere un blocco tra i metodi. Un esempio:

private ReentrantLock lock;

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

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

Tale flusso è impossibile da rappresentare tramite un singolo monitor in un synchronizedcostrutto.


A parte ciò, ReentrantLocksupporta il polling dei blocchi e il blocco interrompibile attende il supporto del timeout . ReentrantLocksupporta anche la politica di equità configurabile , consentendo una pianificazione dei thread più flessibile.

Il costruttore per questa classe accetta un parametro di equità opzionale . Se impostato true, in contesa, i blocchi favoriscono l'accesso al thread più lungo in attesa. In caso contrario, questo blocco non garantisce alcun ordine di accesso particolare. I programmi che usano blocchi equi a cui accedono molti thread possono mostrare un throughput complessivo inferiore (cioè sono più lenti; spesso molto più lenti) rispetto a quelli che usano l'impostazione predefinita, ma hanno varianze minori in tempi per ottenere blocchi e garantire la mancanza di fame. Si noti tuttavia che l'equità dei blocchi non garantisce l'equità della programmazione dei thread. Pertanto, uno dei tanti thread che usano un fair lock può ottenerlo più volte in successione mentre altri thread attivi non stanno procedendo e attualmente non tengono il lock. Si noti inoltre che il senza orariotryLockil metodo non rispetta l'impostazione dell'equità. Avrà successo se il blocco è disponibile anche se altri thread sono in attesa.


ReentrantLock può anche essere più scalabile , con prestazioni molto migliori in contese più elevate. Puoi leggere di più su questo qui .

Questa richiesta è stata contestata, tuttavia; vedi il seguente commento:

Nel test di blocco rientrante, ogni volta viene creato un nuovo blocco, quindi non esiste un blocco esclusivo e i dati risultanti non sono validi. Inoltre, il collegamento IBM non offre alcun codice sorgente per il benchmark sottostante, quindi è impossibile stabilire se il test sia stato condotto correttamente.


Quando dovresti usare ReentrantLocks? Secondo l'articolo di DeveloperWorks ...

La risposta è piuttosto semplice: usala quando in realtà hai bisogno di qualcosa che synchronizednon fornisca , come le attese di blocco temporizzate, le attese di blocco interrompibili, i blocchi non strutturati a blocchi, le variabili a più condizioni o il polling dei blocchi. ReentrantLockha anche vantaggi di scalabilità e dovresti usarlo se hai effettivamente una situazione che presenta un'elevata contesa, ma ricorda che la stragrande maggioranza dei synchronizedblocchi non mostra quasi mai alcuna contesa, per non parlare dell'elevata contesa. Consiglierei di sviluppare con la sincronizzazione fino a quando la sincronizzazione non si sarà dimostrata inadeguata, anziché semplicemente supporre che "le prestazioni saranno migliori" se si utilizzaReentrantLock. Ricorda, questi sono strumenti avanzati per utenti esperti. (E gli utenti veramente avanzati tendono a preferire gli strumenti più semplici che riescono a trovare fino a quando non sono convinti che gli strumenti semplici siano inadeguati.) Come sempre, prima fai la cosa giusta e poi preoccupati se devi farlo più velocemente.


26
Il link "noto per essere più scalabile" a lycog.com dovrebbe essere rimosso. Nel test di blocco rientrante, ogni volta viene creato un nuovo blocco, quindi non esiste un blocco esclusivo e i dati risultanti non sono validi. Inoltre, il collegamento IBM non offre alcun codice sorgente per il benchmark sottostante, quindi è impossibile stabilire se il test sia stato condotto correttamente. Personalmente, rimuoverei l'intera riga sulla scalabilità, poiché l'intera affermazione non è sostanzialmente supportata.
Dev

2
Ho modificato il post alla luce della tua risposta.
oldrinb,

6
Se le prestazioni sono una preoccupazione per te, non dimenticare di cercare un modo in cui NON è necessaria alcuna sincronizzazione.
mcoolive,

2
La cosa della performance non ha alcun senso per me. Se il blocco reantrant funzionasse meglio, perché la sincronizzazione non dovrebbe essere implementata allo stesso modo di un blocco reantrant internamente?
giovedì

2
@ user2761895 il ReentrantLockPseudoRandomcodice nel collegamento Lycog utilizza blocchi nuovissimi setSeede non next
contesi per

14

ReentrantReadWriteLockè un blocco specializzato mentre synchronized(this)è un blocco generico. Sono simili ma non esattamente uguali.

Hai ragione nel fatto che potresti usare synchronized(this)invece di ReentrantReadWriteLockma il contrario non è sempre vero.

Se vuoi capire meglio cosa rende ReentrantReadWriteLockspeciale cercare alcune informazioni sulla sincronizzazione dei thread produttore-consumatore.

In generale puoi ricordare che la sincronizzazione a metodo intero e la sincronizzazione per scopi generici (utilizzando la synchronizedparola chiave) possono essere utilizzate nella maggior parte delle applicazioni senza pensare troppo alla semantica della sincronizzazione, ma se è necessario ridurre le prestazioni dal codice potrebbe essere necessario esplorare altri meccanismi di sincronizzazione più dettagliati o specifici.

A proposito, usare synchronized(this)- e in generale il blocco usando un'istanza di classe pubblica - può essere problematico perché apre il tuo codice a potenziali dead-lock perché qualcun altro non consapevolmente potrebbe provare a bloccare il tuo oggetto da qualche altra parte nel programma.


per evitare potenziali dead-lock perché qualcun altro non consapevolmente potrebbe tentare di bloccare il tuo oggetto da qualche altra parte nel programma usando un'istanza di oggetto privata come sincronizzazione Monitorare in questo modo: public class MyLock { private final Object protectedLongLockingMonitor = new Object(); private long protectedLong = 0L; public void incrementProtectedLong() { synchronized(protectedLongLockingMonitor) { protectedLong++; } } }
sushicutta

9

Dalla pagina della documentazione di Oracle su ReentrantLock :

Un blocco di esclusione reciproca rientrante con lo stesso comportamento di base e semantica del blocco di monitoraggio implicito a cui si accede utilizzando metodi e istruzioni sincronizzati, ma con funzionalità estese.

  1. Un ReentrantLock è di proprietà dell'ultimo thread che si blocca correttamente, ma non lo sblocca ancora. Verrà restituito un thread che invoca il blocco, acquisendo correttamente il blocco, quando il blocco non è di proprietà di un altro thread. Il metodo restituirà immediatamente se il thread corrente possiede già il blocco.

  2. Il costruttore per questa classe accetta un parametro di equità opzionale . Se impostato su true, in contesa, i blocchi favoriscono l'accesso al thread più lungo in attesa . In caso contrario, questo blocco non garantisce alcun ordine di accesso particolare.

Caratteristiche principali di ReentrantLock secondo questo articolo

  1. Possibilità di bloccare in modo interrotto.
  2. Possibilità di timeout durante l'attesa del blocco.
  3. Potere di creare un blocco equo.
  4. API per ottenere un elenco di thread in attesa di blocco.
  5. Flessibilità per provare a bloccare senza bloccare.

È possibile utilizzare ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock per acquisire ulteriore controllo sul blocco granulare nelle operazioni di lettura e scrittura.

Dai un'occhiata a questo articolo di Benjamen sull'uso di diversi tipi di ReentrantLocks


2

È possibile utilizzare i blocchi rientranti con una politica di equità o timeout per evitare la fame di thread. È possibile applicare una politica di equità del thread. aiuterà a evitare che un thread sia in attesa per sempre di raggiungere le tue risorse.

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

La "politica di correttezza" seleziona il thread eseguibile successivo da eseguire. Si basa sulla priorità, tempo dall'ultima corsa, blah blah

inoltre, Synchronize può bloccare indefinitamente se non può sfuggire al blocco. Reentrantlock può avere il timeout impostato.


1

I blocchi sincronizzati non offrono alcun meccanismo di coda di attesa in cui dopo l'esecuzione di un thread qualsiasi thread in esecuzione in parallelo può acquisire il blocco. A causa del quale il thread che è lì nel sistema e in esecuzione per un periodo di tempo più lungo non ha mai la possibilità di accedere alla risorsa condivisa, portando così alla fame.

I blocchi rientranti sono molto flessibili e hanno una politica di equità in cui se un thread è in attesa per un tempo più lungo e dopo il completamento del thread attualmente in esecuzione possiamo assicurarci che il thread in attesa più lungo abbia la possibilità di accedere alla risorsa condivisa diminuendo il throughput del sistema e rendendolo più dispendioso in termini di tempo.


1

Supponiamo che questo codice sia in esecuzione in un thread:

private static ReentrantLock lock = new ReentrantLock();

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

Poiché il thread possiede il blocco, consentirà il blocco di più chiamate (), quindi inserirà nuovamente il blocco. Ciò può essere ottenuto con un conteggio dei riferimenti, quindi non è necessario acquisire nuovamente il blocco.


0

Una cosa da tenere a mente è:

il nome ' ReentrantLock ' dà un messaggio sbagliato su un altro meccanismo di blocco che non rientrano. Questo non è vero. Anche il blocco acquisito tramite "sincronizzato" rientra in Java.

La differenza fondamentale è che "sincronizzato" usa il blocco intrinseco (uno che ogni oggetto ha) mentre l'API di blocco no.


0

Penso che i metodi wait / notification / notifyAll non appartengano alla classe Object in quanto inquina tutti gli oggetti con metodi usati raramente. Hanno molto più senso su una classe Lock dedicata. Quindi, da questo punto di vista, forse è meglio usare uno strumento esplicitamente progettato per il lavoro da svolgere, ovvero ReentrantLock.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.