Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial
  • Lezione 36 di 134
  • livello avanzato
Indice lezioni

Java 8: sincronizzazione e Locks con gli Executor

Impariamo a gestire il problema dell'accesso concorrente a risorse condivise attraverso le nuove API di Java 8.
Impariamo a gestire il problema dell'accesso concorrente a risorse condivise attraverso le nuove API di Java 8.
Link copiato negli appunti

In questo capitolo affrontiamo il problema dell'accesso concorrente a risorse condivise attraverso
le nuove API Java 8. Riprendiamo l'esempio del Produttore-Consumatore ed implementiamolo
attraverso ExecutorService e la classe ReentrantLock che permette un lock dal funzionamento simile a quanto visto con la parola chiave synchronized. Iniziamo col modificare
la classe CubbyHole che rappresenta la nostra risorsa condivisa in modo tale che estenda ReentrantLock:

public class CubbyHole extends ReentrantLock { ... }

Procediamo realizzando un Consumer attraverso implementazione della classe Runnable:

public class ProducerRunnable implements Runnable {
	private final CubbyHole cubbyHole;
	public ProducerRunnable(CubbyHole cubbyHole){
		   this.cubbyHole = cubbyHole;
	}
	@Override
	public void run() {
		while (!Thread.currentThread().isInterrupted()) {
		  cubbyHole.lock();
		  try {
			  if (cubbyHole.isEmpty()) {
						cubbyHole.setEmpty(false);
						System.out.println("Producer: Inserisco qualcosa nel contenitore");
			  }
		  } finally {
			cubbyHole.unlock();
		  }
		}
		System.out.println("Producer: interruzione");
	}
}

Il metodo run() cambia la sua implementazione rispetto a quanto visto in precedenza, in esso notiamo infatti il differente
tentativo di acquisizione del lock() sull'oggetto ReentrantLock condiviso con
il successivo Consumer.

Se l'oggetto non è bloccato, perchè il lock è correntemente acquisito da un altro Thread, l'acquisizione del lock
procede, viene quindi eseguito il codice seguente e rilasciato infine il lock nel blocco finally.

Nel caso in cui il lock sia in possesso
di un Thread concorrente, il Thread corrente si blocca in attesa dell'acquisizione e non è eleggibile per l'esecuzione fino a quando
il lock non viene rilasciato. Continuiamo con l'implementazione del Consumer attraverso la stessa struttura di codice:

public class ConsumerRunnable implements Runnable{
	private final CubbyHole cubbyHole;
	public ConsumerRunnable(CubbyHole cubbyHole){
	   this.cubbyHole = cubbyHole;
	  }
	@Override
	public void run() {
		while (!Thread.currentThread().isInterrupted()) {
			cubbyHole.lock();
			try {
				if (!cubbyHole.isEmpty()) {
						cubbyHole.setEmpty(true);
						System.out.println("Consumer: Prelevo qualcosa dal contenitore");
				}
			} finally {
				cubbyHole.unlock();
			}
	    }
		System.out.println("Consumer: interruzione");
	}
}

Realizziamo infine una classe demo ReentrantLockDemo, che definisca un Executor con due Thread: un Producer e un Consumer. Il codice all'interno
del metodo main della classe demo è il seguente:

...
   CubbyHole cubbyHole = new CubbyHole();
   ProducerRunnable producer = new ProducerRunnable(cubbyHole);
   ConsumerRunnable consumer = new ConsumerRunnable(cubbyHole);
   ExecutorService executor = Executors.newFixedThreadPool(2);
   executor.submit(producer);
   executor.submit(consumer);
   try {
     Thread.sleep(10);
   } catch (InterruptedException ex) {
	 ex.printStackTrace();
   }
   executor.shutdownNow();
   System.out.println("Shutdown completato");
   ...

Evidenziamo la registrazione degli oggetti producer e consumer, la loro esecuzione per 10 ms, e lo shutdown dell'Executor
che arresta i Thread coinvolti. L'esecuzione della classe demo mostrerà in console lo stesso risultato visto nel precedente capitolo.

Esistono altre classi utilizzabili per l'implementazione di un lock, particolarmente interessante è l'interfaccia ReadWriteLock che specifica
un lock per la lettura ed uno per la scrittura, l'idea alla base di esso è che l'accesso multiplo in lettura
ad una risorsa condivisa non necessita di essere Thread safe, cosi una molteplicità di Thread può acquisire contemporaneamente un lock di lettura
se non è attivo un lock di scrittura sulla risorsa.

L'uso del lock di tipo ReadWriteLock è una soluzione particolarmente efficiente quando
le scritture sono poco frequenti e le letture, al contrario, avvengo con molta frequenza. Vediamo un esempio di creazione di un oggetto di questo tipo:

ReadWriteLock lock = new ReentrantReadWriteLock();

Acquisizione del lock di tipo scrittura:

lock.writeLock().lock();

Il suo rilascio:

lock.writeLock().unlock();

Ed analogamente al lock di tipo lettura:

lock.readLock().lock();
lock.readLock().unlock();

Ti consigliamo anche