Per capire come la JMV gestisce la presenza di pià thread in esecuzione contemporaneamente, la cosa migliore da fare è modificare l’esempio precedente e vedere l’output a schermo relativo. Dunque modificheremo la classe "EsempioThread" nel modo seguente:
public class EsempioThread implements Runnable {
private int numeroThread;
public EsempioThread(int n) {
setNumeroThread(n);
}
public void setNumeroThread(int n) {
numeroThread=n;
}
public int getNumeroThread() {
return numeroThread;
}
public void run() {
int n;
for(n=0;n<6;n++) {
System.out.println("*** thread numero"+ getNumeroThread() + " in esecuzione");
System.out.println("l'indice n vale:" + n);
try {
Thread.sleep(1000);
}
catch (InterruptedException exc) {
System.out.println("Errore");
}
}
}
}
La variabile di classe "numeroThread" serve per identificare quale thread è in esecuzione. Da notare il metodo Thread.sleep() che permette di sospedere il thread per un tempo pari al valore passato come parametro (il valore temporale deve essere espresso in millisecondi). Il metodo può sollevare un'eccezione di tipo InterruptedException e pertanto abbiamo gestito l'eccezione con l'uso del blocco try e catch che abbiamo spiegato nelle lezioni precedenti. Andiamo adesso a modificare la classe di implementazione andando a lanciare due thread contemporanemanete:
public class Implementazione {
public static void main (String[] args) {
EsempioThread r1 = new EsempioThread(1);
EsempioThread r2 = new EsempioThread(2);
Thread nuovoThread1 = new Thread(r1);
Thread nuovoThread2 = new Thread(r2);
nuovoThread1.start();
nuovoThread2.start();
}
}
Abbiamo passato i valori 1 e 2 al costruttore della classe "EsempioThread" per poter identificare i due thread al momento dell'esecuzione. Se lanciamo il seguente programma avremo un output a video un po' particolare (riferito solo a due iterazioni):
*** thread numero 1 in esecuzione
l'indice n vale:0
*** thread numero 2 in esecuzione
l'indice n vale:0
*** thread numero 1 in esecuzione
l'indice n vale:1
*** thread numero 2 in esecuzione
l'indice n vale:1
Leggendo l'output del programma si capisce immediatamente il vantaggio di aver strutturato l'applicazione con l'uso dei thread. Infatti il thread 1 va in esecuzione e compie la prima iterazione, poi si blocca per un secondo. A quel punto non si aspetta che passi il secondo di sospensione del thread, ma bensì la JVM va ad eseguire il secondo thread che dopo la sua iterazione va in sleep. Si capisce bene che, senza l'uso dei thread, ciò non sarebbe stato possibile e avremo dovuto aspettare che la prima istanza della classe eseguisse tutte le iterazioni, con le relative pause, per poi passare ad eseguire la seconda istanza. Questa approccio ovviamente è altamente sconsigliato dato che, di fatto, per la maggior parte dell'esecuzione l'applicazione è sospesa e la CPU è inattiva.
Ricapitolando, nel nostro esempio, quando un thread viene sospeso viene passato il controllo all'altro e viceversa. Ma cosa succede se eliminiamo lo sleep nel thread? Dunque come prima cosa riprendiamo la classe "EsempioThread" e, nel metodo run(), eliminiamo il blocco try catch dove all'interno è presente il metodo che sospende il thread. A questo punto editiamo anche il ciclo for sempre all'interno del metodo run() mettendo come limite di iterazioni 400 (comunque un valore dell'ordine delle centinaia) e lanciamo la nostra applicazione. Controllando l'output vedremo che il thread 1 inizierà a compiere le proprie iterazioni e quindi stamperà a video 1,2,3,4 ecc. ecc. Ad un certo punto però l'output diviene il seguente:
*** thread numero 1 in esecuzione
l'indice n vale:273
*** thread numero 1 in esecuzione
l'indice n vale:274
*** thread numero 2 in esecuzione
l'indice n vale:0
*** thread numero 2 in esecuzione
l'indice n vale:1
Cosa è successo? Semplicemente il thread 1 dopo aver svolto alcune iterazioni, viene sospeso automaticamente dalla JVM per dare spazio al thread 2 che inizierà a compiere la sue iterazioni. Successivamente il thread 2 verrà sospeso e verrà ripassato il controllo al thread 1 e il tutto continuerà fino a che entrambi i thread terminano l'esecuzione.
Quello che ha compiuto la JVM è detto scheduling dei thread ovvero l'operazione di scelta a run-time di quale thread sospedere e quale attivare. Il processo di scheduling viene utilizzato da tutti i sistemi operativi moderni per permettere il multi-tasking e quindi l'esecuzione di più programmi contemporaneamente. La cosa importante da sottolineare è che la scelta di quale e quando sospendere un determinato thread o processo dipende da moltissimi fattori e la spiegazione di come agiscono gli scheduler esula dallo scopo della guida. Detto questo, se noi proviamo a rilanciare l'applicazione appena scritta, non è detto che il thread 1 si interromperà all'iterazione 274, ma si può interrompere prima oppure dopo, tutto dipende dalla presenza di altri processi che girano sulla vostra macchina.A questo punto della guida abbiamo dato tutti gli strumenti necessari per poter scrivere applicazioni complesse e di grosse dimensione. Studiata la teoria l'unico modo per poter imparare veramente a programmare è fare esperienza, provare, sbagliare e correggere.