Il linguaggio Java implementa una forma semplificata dei Monitor. Come prima cosa vediamo come creare un thread in Java.
Creazione di Thread in Java
I thread in Java vengono creati estendendo la classe Thread
e definendo il metodo run
(il codice che il thread eseguirà). Per eseguire il nuovo thread è sufficiente invocare il metodo start
.
public class CreaThread extends Thread { public void run() { System.out.println("Saluti dal thread " + this.getName()); } public static void main(String args[]) { CreaThread t = new CreaThread(); t.start(); } }
Oppure, se c’è esigenza di estendere altre classi, i thread possono anche essere creati come oggetti della classe Thread
, al cui costruttore viene passato un oggetto che implementa l’interfaccia Runnable
, che richiede l’implementazione del metodo run
.
public class CreaThread2 implements Runnable { public void run() { System.out.println("Saluti dal thread " + Thread.currentThread().getName()); } public static void main(String args[]) { CreaThread2 r = new CreaThread2(); Thread t = new Thread(r); t.start(); } }
Notare che in questo caso per stampare il nome del thread si deve usare il metodo currentThread()
della classe Thread
. Nella prima soluzione, invece, possiamo direttamente invocare getName()
sull’oggetto corrente in quanto istanza della classe Thread
.
Entrambi i programmi danno il seguente output:
> javac CreaThread.java > java CreaThread Saluti dal thread Thread-0 >
Come per i thead POSIX, possiamo anche “addormentare” un thread Java, in questo caso il tempo è dato in millisecondi ed è necessario gestire l’eccezione InterruptedException
sollevata da sleep nel caso il thread t
venga interrotto ad esempio tramite t.interrupt()
.
public void run() { try { sleep(1000); // attende un secondo } catch(InterruptedException e) { System.out.println("["+getName()+"]"+" Ah mi hanno interrotto!!"); return; }
Esercizio 1: creazione, interruzione e attesa di thread
Creare n thread, rallentarli tramite sleep()
, provare ad interromperne l’attesa utilizzando t.interrupt()
e attendere la terminazione con t.join()
(analogamente a quanto si fa con i thread POSIX), dove t
è l’oggetto thread. Aggiungere opportune print per osservare l’esecuzione.
I Monitor di Java
In Java è implementata una forma semplificata dei monitor con le seguenti caratteristiche:
- Ogni oggetto ha un mutex implicito utilizzato per garantire mutua esclusione sui metodi;
- I metodi sono eseguiti in mutua esclusione solo se dichiarati
synchronized
; - Ogni oggetto ha un’unica condition implicita sulla quale si possono effettuare le operazioni standard
wait(), notify(), notifyAll()
; - se il metodo è statico allora il mutex è a livello di classe invece che di oggetto;
- è inoltre possibile sincronizzare parti di codice di un metodo non synchronized nel seguente modo:
synchronized(this) { contatore=contatore+1 }
this
indica a quale oggetto fare riferimento per ottenere il lock implicito. In questo caso è l’oggetto stesso ma è possibile indicare oggetti differenti e quindi sincronizzarsi utilizzando il mutex di tali oggetti.
NOTA: la sincronizzazione è “rientrante”: è possibile chiamare un metodo synchronized
da un altro synchronized
senza problemi (il mutex è già acquisito alla chiamata del primo metodo sincronizzato e viene mantenuto fino al ritorno del metodo o ad una eventuale terminazione prematura per via di un’eccezione non gestita)
Esercizio 2: sezione critica
Implementare due thread che lavorano su un contatore condiviso, osservare le usuali interferenze e rimediare ponendo il codice all’interno di metodi synchronized
.
Suggerimento: mettere il contatore in una classe apposita (che fungerà da monitor) e implementare i metodi per l’incremento e la lettura/stampa del valore.
Esercizio 3: produttore e consumatore
Implementare il problema del produttore e consumatore in Java. Testarlo con tanti produttori e tanti consumatori.
Suggerimento: seguire lo schema illustrato nella lezione sui monitor.