Esempio di blocco Java – ReentrantLock

Benvenuti al tutorial sull’esempio di Java Lock. Di solito, quando si lavora in un ambiente multi-threaded, utilizziamo il synchronized per la sicurezza del thread.

Blocco Java

La maggior parte delle volte, la parola chiave synchronized è il modo migliore, ma ha alcune limitazioni che hanno portato all’inclusione dell’API Lock nel pacchetto Concurrency di Java. Java 1.5 Concurrency API ha introdotto il pacchetto java.util.concurrent.locks con l’interfaccia Lock e alcune classi di implementazione per migliorare il meccanismo di blocco dell’oggetto. Alcune interfacce e classi importanti nell’API Lock di Java sono:

  1. Lock: Questa è l’interfaccia di base per l’API Lock. Fornisce tutte le funzionalità della parola chiave synchronized con modi aggiuntivi per creare diverse Condizioni per il blocco, fornendo un timeout per il thread in attesa del blocco. Alcuni dei metodi importanti sono lock() per acquisire il blocco, unlock() per rilasciare il blocco, tryLock() per attendere il blocco per un certo periodo di tempo, newCondition() per creare la Condizione, ecc.

  2. Condizione: Gli oggetti Condizione sono simili al modello di attesa-notifica dell’oggetto con la funzionalità aggiuntiva di creare diversi insiemi di attese. Un oggetto Condizione è sempre creato da un oggetto Blocco. Alcuni dei metodi importanti sono await(), simile a wait(), e signal(), signalAll(), simili ai metodi notify() e notifyAll().

  3. Blocco di Lettura/Scrittura: Contiene una coppia di blocchi associati, uno per operazioni di sola lettura e un altro per la scrittura. Il blocco di lettura può essere detenuto contemporaneamente da più thread lettori fintanto che non ci sono thread scrittori. Il blocco di scrittura è esclusivo.

  4. ReentrantLock: Questa è la classe di implementazione più ampiamente usata dell’interfaccia Lock. Questa classe implementa l’interfaccia Lock in modo simile alla parola chiave synchronized. Oltre all’implementazione dell’interfaccia Lock, ReentrantLock contiene alcuni metodi di utilità per ottenere il thread che detiene il blocco, i thread in attesa di acquisire il blocco, ecc. I blocchi synchronized sono ricorsivi per natura, ovvero se un thread ha il blocco sull’oggetto monitor e se un altro blocco synchronized richiede di avere il blocco sullo stesso oggetto monitor, allora il thread può entrare in quel blocco di codice. Penso che questa sia la ragione per cui il nome della classe è ReentrantLock. Capiamo questa caratteristica con un semplice esempio.

    public class Test{
    
    public synchronized foo(){
        //fai qualcosa
        bar();
      }
    
      public synchronized bar(){
        //fai altro
      }
    }
    

    Se un thread entra in foo(), ha il blocco sull’oggetto Test, quindi quando cerca di eseguire il metodo bar(), al thread viene consentito di eseguire il metodo bar() poiché detiene già il blocco sull’oggetto Test, ovvero lo stesso di synchronized(this).

Esempio di Lock Java – ReentrantLock in Java

Ora vediamo un esempio semplice in cui sostituiremo la parola chiave synchronized con l’API Lock di Java. Supponiamo di avere una classe Risorsa con alcune operazioni in cui vogliamo che sia thread-safe e alcuni metodi in cui la sicurezza dei thread non è richiesta.

package com.journaldev.threads.lock;

public class Resource {

	public void doSomething(){
		//fai qualche operazione, lettura DB, scrittura ecc
	}
	
	public void doLogging(){
		//registrazione, non c'è bisogno di sicurezza dei thread
	}
}

Ora supponiamo di avere una classe Runnable in cui utilizzeremo i metodi di risorsa.

package com.journaldev.threads.lock;

public class SynchronizedLockExample implements Runnable{

	private Resource resource;
	
	public SynchronizedLockExample(Resource r){
		this.resource = r;
	}
	
	@Override
	public void run() {
		synchronized (resource) {
			resource.doSomething();
		}
		resource.doLogging();
	}
}

Notare che sto utilizzando un blocco sincronizzato per acquisire il blocco sull’oggetto Risorsa. Avremmo potuto creare un oggetto fittizio nella classe e usarlo a tale scopo. Ora vediamo come possiamo utilizzare l’API di blocco di Java e riscrivere il programma precedente senza utilizzare la parola chiave synchronized. Utilizzeremo ReentrantLock in Java.

package com.journaldev.threads.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConcurrencyLockExample implements Runnable{

	private Resource resource;
	private Lock lock;
	
	public ConcurrencyLockExample(Resource r){
		this.resource = r;
		this.lock = new ReentrantLock();
	}
	
	@Override
	public void run() {
		try {
			if(lock.tryLock(10, TimeUnit.SECONDS)){
			resource.doSomething();
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			//rilascia il blocco
			lock.unlock();
		}
		resource.doLogging();
	}

}

Come puoi vedere, sto utilizzando il metodo tryLock() per assicurarmi che il mio thread attenda solo per un tempo definito e, se non ottiene il blocco sull’oggetto, viene semplicemente registrato e terminato. Un altro punto importante da notare è l’uso del blocco try-finally per assicurarsi che il blocco venga rilasciato anche se la chiamata al metodo doSomething() genera un’eccezione.

Java Lock vs synchronized

Basandoci sui dettagli e sul programma precedenti, possiamo facilmente concludere le seguenti differenze tra Java Lock e la sincronizzazione.

  1. L’API di blocco di Java offre maggiore visibilità e opzioni per il blocco, a differenza della sincronizzazione in cui un thread potrebbe finire per attendere indefinitamente il blocco, possiamo utilizzare tryLock() per assicurarci che il thread attenda solo per un tempo specifico.
  2. Il codice di sincronizzazione è molto più pulito e facile da mantenere, mentre con Lock siamo costretti ad avere un blocco try-finally per assicurarci che il Lock venga rilasciato anche se si verifica un’eccezione tra le chiamate ai metodi lock() e unlock().
  3. blocchi o metodi di sincronizzazione possono coprire solo un metodo, mentre possiamo acquisire il blocco in un metodo e rilasciarlo in un altro metodo con l’API di Lock.
  4. La parola chiave synchronized non fornisce equità, mentre possiamo impostare l’equità su true durante la creazione dell’oggetto ReentrantLock in modo che il thread in attesa più a lungo ottenga per primo il blocco.
  5. Possiamo creare condizioni diverse per il Lock e diversi thread possono attendere() per condizioni diverse.

Questo è tutto per l’esempio di Lock in Java, ReentrantLock in Java e un’analisi comparativa con la parola chiave synchronized.

Source:
https://www.digitalocean.com/community/tutorials/java-lock-example-reentrantlock