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 ()); |
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
La stessa cosa alla classe Prelevare
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.