Multithreading

Classe Thread

Ci permette di lanciare un’applicazione contestualmente con la principale,  un esempio:

Nel ThreadUno invochiamo il metodo start che richiede la registrazione nel Thread Scheduler il quale determina quale thread deve essere in esecuzione.

Un modo alternativo consiste nell’utilizzo dell’interfaccia eseguibile.  Queste sono le piccole modifiche da fare

public class ThreadUno implements Runnable {

mentre nel main

Thread threadA = new Thread (new ThreadUno ());
threadA.start ();

Con Java 8 si ha una migliore gestione dei thread. Executor sostituisce la modalità di realizzazione diretta di thread consentendo l’esecuzione di task asincroni e pool di thread (ogni thread nel pool rimane in attesa di nuovi task) .

Aggiungiamo con submit() un solo Thread implementato attraverso una classe anonima con Runnable.

ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(new Runnable () {

Altri metodi sono:

  • newCachedThreadPool () – un pool Thread che cresce dinamicamente riutilizzando i Thread creati;
  • newFixedThreadPool () – un pool Thread a dimensione fissa;
  • newScheduledThreadPool () – un pool Thread che eseguono task dopo un certo intervallo di tempo o periodicamente;

Possiamo realizzare un pool di due Thread con queste piccole modifiche

ExecutorService executor = Executors.newFixedThreadPool (2);

e poi ripetere per due volte

executor.submit(new Runnable () {

Se abbiamo bisogno di un metodo che ritorna un valore utilizziamo fei riferimenti di tipo Callable che ci da la possibilità di monitorare lo stato di esecuzione di un Thread sfruttando il metodo isDone ().

ExecutorService executor = Executors.newSingleThreadExecutor();
Future future = executor.submit(new Callable () {

Ricapitolando un Thread, dopo l’invocazione del metodo start (), passa nello stato Ready fino a quando non viene selezionato dallo Scheduler e arriva allo stato running.

Il Thread può passare in questi stati quando si interrompe:

  • non running states – blocked, suspended e sleeping;
  • dead – ha completato l’esecuzione del metodo run;
  • blocked – in attesa di una risorsa occupata da un altro thread;
  • suspended – tramite il metodo suspend (), deprecato perché può causare deadlock;

Ogni oggetto ha un lock che può essere controllato da un solo Thread, un successivo Thread che richiede lo stesso lock passa in stato Seeking lock fino a quando il Thread che aveva il possesso lo rilascia.

La sincronizzazione del codice condiviso può avvenire in:

  • sincronizzare un intero metodo;
  • sincronizzare un blocco;

I metodi della classe object sono wait, notify e notifyall. L’invocazione del metodo wait fa passare il Thread allo stato waiting, verrà risvegliato dall’invocazione di notify o notifyall. L’invocazione di notify  sveglierà un Thread scelto dallo scheduler.

Vediamo un esempio, creiamo una classe Scatola con due metodi

Implementa un contenitore dove Inserire inserirà un gettone che preleve Prelevare

Infine nel main bisogna istanziare i due oggetti

Inserire inserire = new Inserire (scatola);
Prelevare prelevare = new Prelevare (scatola);

lanciarli e poi arrestarli

inserire.start ();
prelevare.start ();

inserire.interrupt ();
prelevare.interrupt ();

Ora analizziamo il problema dell’accesso concorrente a risorse condivise. Modifichiamo la classe Scatola estendendola a ReentrantLock

public class Scatola extends ReentrantLock {

Modificare la classe Inserire

public class InserireRunnable implements Runnable

@Override
public void run () {
while (!Thread.currentThread().isInterrupted()) {
scatola.lock();
try {
if (scatola.isEmpty()) {
scatola.setEmpty(false);
System.out.println(“Inserisco gettone”);
}
} finally {
scatola.unlock();
}
}
System.out.println(“Interruzione”);
}

La stessa cosa alla classe Prelevare

public class PrelevareRunnable implements Runnable {

@Override
public void run () {
while (!Thread.currentThread().isInterrupted()) {
scatola.lock();
try {
if (!scatola.isEmpty()) {
scatola.setEmpty(true);
System.out.println(“Prelevo gettone”);
}
} finally {
scatola.unlock();
}
}
System.out.println(“Interruzione”);
}

Infine il main diventa


InserireRunnable inserire = new InserireRunnable (scatola);
PrelevareRunnable prelevare = new PrelevareRunnable (scatola);

executor.submit (inserire);
executor.submit (prelevare);

L’interfaccia ReadWriteLock permette di avere un lock per la scrittura ed uno multiplo per la lettura se non è attivo il lock della scrittura.

Please follow and like us:

Lascia un commento