Home - Gianmarco-Rampulla-JCMaxwell-4Bi/MultiThreading GitHub Wiki
I concetti imparati dallo sviluppo del programma sono i seguenti:
- cosa è un Thread,
- creazione e dichiarazione di un Thread,
- uso di thread.start(),
- uso di thread.join(),
- uso di thread.interrupt(),
- interrompere temporaneamente thread usando TimeUnit.MILLISECONDS.sleep(),
- generazione numeri random,
- concetto di variabile statica,
- utilizzo della notazione @Override,
- come accedere correttamente alle variabili utilizzando i Threads.
Alla fine della pagina è inoltre presente un esempio di esecuzione del programma su NetBeans oltre che un esempio di come l'accesso corretto alle variabili possa influenzare realmente sul funzionamento del programma eseguito sempre sullo stesso IDE.
https://github.com/Gianmarco-Rampulla-JCMaxwell-4Bi/MultiThreading/wiki/Progetto-Filosofi-a-cena
Un Thread è una suddivisione di un programma in uno o più sottoparti in modo da poter essere seguite in modo concorrente. Un sistema in grado di eseguire Thread viene chiamato sistema multi-threading.
Sono molto utili quando c'è la necessità di dover eseguire più operazioni contemporaneamente come ad esempio quando bisogna gestire l'interfaccia grafica di un programma mentre si esegue una ricerca di un valore dato in input.
Senza l'ausilio dei thread il thread principale, che prima si occupava della finestra del programma, effettuerebbe la ricerca dell'input non rispondendo ad esempio al clic su un bottone dell'interfaccia in quanto già occupato.
Quindi per ovviare al problema lo si scompone dando ad un thread la gestione dell'interfaccia e ad un altro l'incarico di effettuare la ricerca.
Un po' come in un ristorante: con un solo cameriere (che in questo caso rappresenta solo il programma senza thread), molti clienti resterebbero molto tempo ad aspettare, quindi, si dà a più camerieri il compito di servire clienti diversi velocizzando il tempo richiesto per accontentarli tutti (mentre in questo ultimo esempio i camerieri rappresenterebbero un programma con più thread).
In Java abbiamo appreso che per dichiarare un thread e quindi crearlo bisogna scrivere il seguente codice:
Thread NomeThread = new Thread(new ClasseCheImplementaRunnable(EventualiParametri));
Dove "ClasseCheImplementaRunnable" è una classe personalizzata che implementa Runnable.
Come ad esempio:
class ProvaPerGitHub implements Runnable{
}
Gli "EventualiParametri" ovviamente dipendono dal costruttore della classe che implementa Runnable.
In caso si costruisse una classe con questo tipo di costruttore:
class ProvaPerGitHub implements Runnable{
public ProvaPerGitHub(String Messaggio)
{
}
}
In questo caso la dichiarazione del thread dovrà contenere anche i parametri da passare al costruttore della classe che implementa Runnable.
Usando questo esempio la dichiarazione di un thread con questa classe sarebbe:
Thread NomeThread = new Thread(new ProvaPerGitHub("Ciao"));
Curiosità: si può, al posto di scrivere implements Runnable, ereditare la classe Thread utilizzando la keyword extends
.
class ProvaPerGitHub extends Thread{
public ProvaPerGitHub(String Messaggio)
{
}
}
Una volta dichiarati i Thread, prima di poter vedere veramente una suddivisione del lavoro, bisogna farli partire.
Per farlo, in Java, si utilizza il comando Thread.start().
NomeThread.start() //faccio partire il rispettivo thread
Questa procedura, però, non fa altro che invocare il metodo run() all'interno della classe passata come parametro alla creazione del Thread, che deve per forza contenere un metodo con lo stesso nome all'interno della classe.
Infatti all'interno della classe del thread (quella che implementa Runnable) bisogna aggiungere:
class ProvaPerGitHub implements Runnable{
public ProvaPerGitHub(String Messaggio)
{
}
@Override //verra' spiegato dopo
public run()
{
//codice da far eseguire all'avvio del Thread
}
}
Per fare in modo che all'invocazione di start() il thread faccia qualcosa basta inserire il codice all'interno di run().
Dal momento dell'avvio il thread sarà completamente indipendente dal thread principale (il programma all'avvio) e smetterà di esistere o quando avrà finito ciò che deve fare o in casi particolari in cui verrà richiesta l'interruzione.
Curiosità: l'esempio è valido anche per le classi che estendono Thread ma è, comunque, consigliabile utilizzare implements Runnable
per alcuni vantaggi.
Ad esempio:
- se si estende Thread non si potrebbero più ereditare altre classi (Java non supporta l'ereditarietà da classi multiple) mentre con
implements Runnable
, essendo un'interfaccia, si potrebbe ereditare anche altre classi.
Per maggiori informazioni: https://manikandanmv.wordpress.com/tag/extends-thread-vs-implements-runnable/
Dal momento dall'avvio di un thread, il processo principale ovvero il programma continua la sua esecuzione. Quindi se dopo la chiamata allo start() dei vari thread c'e' del codice verrà eseguito.
Ma se volessimo aspettare che un particolare thread o magari tutti i thread finiscano prima di continuare con l'esecuzione del processo principale o di un thread in generale?
Ecco che ci viene in aiuto thread.join().
Questo metodo permette, infatti, di aspettare che un thread finisca per andare avanti con l'elaborazione del thread da cui è invocato.
public class ProvaPerGitHubMain
{
public static void Main(String[] args)
{
Thread Prova1 = new Thread(new ProvaPerGitHub("StringaInutile"));
Prova1.start(); //esegue il thread
//non attende che il thread finisce
System.out.println("Fine di tutti i thread") //viene eseguito subito
}
}
class ProvaPerGitHub implements Runnable{
public ProvaPerGitHub(String Messaggio)
{
}
@Override //verra' spiegato dopo
public run()
{
System.out.println("Esecuzione Thread!"); //codice di prova
}
}
public class ProvaPerGitHubMain
{
public static void Main(String[] args)
{
Thread Prova1 = new Thread(new ProvaPerGitHub("StringaInutile"));
Prova1.start(); //esegue il thread
Prova1.join(); //ferma l'esecuzione del programma e continua quando Prova1 ha finito o viene interrotto
System.out.println("Fine di tutti i thread") //viene eseguito dopo il join
}
}
class ProvaPerGitHub implements Runnable{
public ProvaPerGitHub(String Messaggio)
{
}
@Override //verra' spiegato dopo
public run()
{
System.out.println("Esecuzione Thread!"); //codice di prova
}
}
Ultima cosa ma non meno importante riguarda il try-catch con cui è necessario circondare thread.join().
public class ProvaPerGitHubMain
{
public static void Main(String[] args)
{
Thread Prova1 = new Thread(new ProvaPerGitHub("StringaInutile"));
Prova1.start(); //esegue il thread
try
{
Prova1.join(); //ferma l'esecuzione del programma e continua quando Prova1 ha finito o viene interrotto
}
catch (InterruptedException ex) {
System.out.println("Thread Interrotto!"); //se il thread da cui si e' invocata la procedura join viene interrotto
viene segnalato
}
}
}
class ProvaPerGitHub implements Runnable{
public ProvaPerGitHub(String Messaggio)
{
}
@Override //verrà spiegato dopo
public run()
{
System.out.println("Esecuzione Thread!"); //codice di prova
}
}
L'eccezione InterruptedException viene lanciata quando il thread da cui viene invocato join() viene interrotto.
thread.interrupt(), invece, è totalmente l'opposto di join().
Infatti, se è possibile cerca di fermare prematuramente il thread da cui viene invocato.
In verità non interrompe immediatamente il thread in quanto farlo non è sicuro e porterebbe più svantaggi che vantaggi.
Bensì attiva un flag (una variabile booleana) all'interno del thread in modo da far capire che il thread dovrebbe essere interrotto. Quindi solo in casi possibili all'attivazione del flag il thread viene realmente interrotto. per fare in modo che l'interruzione sia possibile bisogna scrivere del codice in modo da controllare il flag e indirizzare il thread in una corretta interruzione.
-
Metodi bloccanti:
//in questo caso, se invoco Prova1.interrupt() try { Prova1.sleep((long) 10000); // metodo bloccante che lancia un eccezione se viene richiesta l'interruzione } catch (InterruptedException e) { //viene catturata l'eccezione System.out.println("Thread Terminato"); return //il thread ritorna cioè si interrompe }
-
Controllo nel run():
@Override public void run() { //nel run() della classe del thread while (!Thread.currentThread().isInterrupted()) { //si esegue un controllo per cui si esegue il codice solo se il flag di interrupted() non è attivo }//se il codice viene eseguito solo quando non e' interrotto, se lo diventa esce dal ciclo }//uscito dal ciclo, se non è presente ulteriore codice uscirà dal run() e si chiuderà il thread
TimeUnit.MILLISECONDS.sleep() permette di fermare temporaneamente il thread da cui è stato invocato.
Come parametro richiede il numero, in questo caso, di millisecondi.
TimeUnit.MILLISECONDS.sleep(300) //ferma il thread corrente per 300 millisecondi
E' simile a thread.sleep() trattato precedentemente e come quest'ultimo deve essere circondato da try-catch
try {
TimeUnit.MILLISECONDS.sleep(300); //aspetta tempo casuale
} catch (InterruptedException e) {
//inserire codice da eseguire in caso di interruzione
}
Quindi anche in questo caso se nel thread che sta "dormendo" viene attivato il flag del thread.interrupt() verrà catturata l'eccezione in modo da poter eseguire del codice per gestire la richiesta di interruzione.
Curiosità: TimeUnit è una classe che contiene altre unità di misura come SECONDS o MINUTES quindi se voglio aspettare un minuto posso scrivere:
TimeUnit.MINUTES.sleep(nminuti) //nminuti può essere una variabile o, ovviamente, un valore già definito
NB: per usare TimeUnit bisogna importare la seguente libreria: java.util.concurrent.TimeUnit
Uno dei modi possibili per generare numeri casuali è utilizzare Math.random().
NumeroDaCuiPartire + (Math.random() * NumeroMassimo);
Dove NumeroDaCuiPartire rappresenta il minimo valore da cui partire a sorteggiare (può essere sia una variabile che un valore già definito).
Mentre NumeroMassimo rappresenta il valore massimo che può uscire dal sorteggio (può essere sia una variabile che un valore già definito anche in questo caso).
int Random = 10 + (Math.random() * 100); //sorteggia un numero casuale da 10 a 100
Può essere anche usato in questi modi:
-
Solo specificando il NumeroMassimo
int Random = Math.random() * 100; //sorteggia un numero casuale da 0 a 100
-
Usando solo Math.random()
int Random = Math.random(); //sorteggia un numero casuale da 0 a 1
Curiosità: è possibile generare numeri casuali anche attraverso la classe Random.
Per usare questa classe bisogna importare java.util.Random.
-
Istanziare oggetto di Classe Random:
Random C = new Random();
-
usare la funzione nextInt() per generare un numero intero casuale:
int k = random.nextInt((NumeroMassimo))+NumeroDaCuiPartire;
Dove:
- NumeroMassimo è opzionale ed indica il numero massimo entro cui estrarre il numero.
- NumeroDaCuiPartire è opzionale ed indica il numero minimo entro cui estrarre il numero.
Curiosità: La classe Random contiene anche metodi per sorteggiare casualmente valori Boolean, Float e molti altri.
Le variabili statiche, al contrario di quelle normali, vengono istanziate solo una volta quindi ne è disponibile solo una istanza per tutta la classe in cui risiede.
Vengono usate per scambiare informazioni tra i thread in quanto, essendo statica e quindi non avendo copie, tutti i thread possono accendere a questo tipo di variabile.
Per dichiarare una variabile statica in Java:
(ModificatoreDiAccesso) static TipoVariabile NomeVariabile;
Dove
-
ModificatoreDiAccesso sarebbe la
keyword
che indica la visibilità della variabile(ad esempio: public, private, protected), si può anche omettere. -
TipoVariabile sarebbe il tipo di dato (esempio: int, String ecc).
-
NomeVariabile rappresenta il nome con cui poi richiamare la variabile.
Per accedere a queste variabili, se sono static, public e contenute in altre classi, si dovrà usare la notazione "NomeClasse"."NomeVariabileStatica" per accedere dall'esterno alla classe contenente la variabile statica
public class ProvaPerGitHub
{
public static int ciao = 0; //dichiarazione variabile intera
}
class VoglioUnaVariabileStatic
{
//se voglio accedere alla variabile "ciao" contenuta in "ProvaPerGitHub"
//essendo all'esterno della classe in cui è stata dichiarata
//devo scrivere:
ProvaPerGitHub.ciao = 3; //assegnazione per dimostrare l'accesso alla variabile
//NB: questa notazione funziona solo se la variabile è dichiarata come pubblica
//altrimenti non funzionerebbe in quanto non sarebbe visibile all'esterno della classe in cui è stata dichiarata
}
Se invece si accede dalla stessa classe in cui è stata dichiarata, si utilizza come una variabile normale.
public class ProvaPerGitHub
{
public static int ciao = 0; //dichiarazione variabile intera
public static void Main(String[] args)
{
ciao = 3; //non c'è bisogno di scrivere di nuovo classe, essendo il metodo all'interno della classe con la variabile statica
}
}
NB: le variabile statiche non possono essere dichiarate nei metodi, ma solo all'interno di una classe e fuori da qualsiasi metodo
public class ProvaPerGitHub
{
public static int ciao = 0; //dichiarazione corretta
private void NonPuoi()
{
public static int ciao = 0; //dichiarazione incorretta
}
}
La notazione @Override, serve per sovrascrivere un metodo che è stato ereditato da una classe o implementato da un interfaccia.
In questo modo, il metodo di cui è stato effettuato l'override è indipendente dal metodo della classe padre (da cui eredita).
Se non scrivessimo @Override il compilatore non sovrascriverebbe il metodo ma effettuerebbe un Overload del metodo generando un errore.
Per Overload del metodo si intende un metodo con lo stesso nome ma con una firma differente.
In semplici parole è come avere tanti ristoranti che servono la stessa pietanza ma richiedono metodi di pagamento diversi.
class ProvaPerGitHub implements Runnable //implementa un interfaccia, richiede l'implementazione di alcuni metodi
obbligatoriamente
{
@Override //notazione override, indica al compilatore che deve sovrascrivere questo metodo implementato
dall'interfaccia Runnable, se non ci fosse il compilatore lancerebbe un eccezione in quanto deve essere per forza overridato
public void run() { //nel run() della classe del thread
}
}
Per ulteriori informazioni sul motivo per cui è consigliabile usare @Override -> http://lancill.blogspot.it/2012/11/annotations-override.html
In caso non riuscissi a vedere la foto qui trovi la sua trascrizione:
run:
Main Thread iniziata...
<TOE> TOE: 10
<TAC> TAC: 10
<TIC> TIC: 10
<TOE> TOE: 9
<TAC> TAC: 9
<TIC> TIC: 9
<TOE> TOE: 8
<TAC> TAC: 8
<TOE> TOE: 7
<TIC> TIC: 8
<TAC> TAC: 7
<TOE> TOE: 6
<TIC> TIC: 7
<TOE> TOE: 5
<TAC> TAC: 6
<TIC> TIC: 6
<TOE> TOE: 4
<TAC> TAC: 5
<TOE> TOE: 3
<TIC> TIC: 5
<TAC> TAC: 4
<TOE> TOE: 2
<TIC> TIC: 4
<TAC> TAC: 3
<TOE> TOE: 1
<TIC> TIC: 3
<TAC> TAC: 2
<TAC> TAC: 1
<TIC> TIC: 2
<TIC> TIC: 1
Thread Terminati!
Punteggio: 5
Main Thread completata! tempo di esecuzione: 2934ms
BUILD SUCCESSFUL (total time: 3 seconds)
Anche se le variabili statiche sono un metodo efficace per scambiare dati tra i threads, molte volte capita che i due thread eseguino lo stesso pezzo di codice contemporaneamente.
Eseguendo lo stesso pezzo di codice contemporaneamente entrambi possono modificare le stesse variabili, rischiando di sovrascrivere dati già presenti che potrebbero essere molto importanti per la correttezza del programma.
Possiamo paragonare questa situazioni a due professori che correggono un unico compito, entrambi sovrascrivono le correzioni dell'altro non ottenendo un risultato unico e giusto.
Per risolvere bisogna fare in modo che i threads eseguino uno alla volta il pezzo di codice da eseguire.
Per farlo bisogna inserire la parola chiave synchronized
quando si dichiara una procedura all'interno della classe che viene definita, al momento della dichiarazione di un metodo synchronized, come classe Monitor.
public synchronized void Spiegazione()
{
//codice
}
Con la parola chiave synchronized
solo un thread alla volta può eseguire il codice all'interno della procedura e modificare le variabili interne alla classe, evitando cosi che si verifichi l'esempio di prima.
Il funzionamento dei metodi synchronized
è simile a quello di una coda, ovvero il primo thread che richiede l'accesso esegue il codice, gli altri thread aspettano il loro turno fino a quando non viene liberato l'accesso ed eseguire a loro volta il codice.
Come ho già detto la classe che contiene dei metodi synchronized
è detta classe Monitor
class IoControlloIThread //normale dichiarazione di una classe
{
private synchronized void Ciao() //essendoci un metodo synchronized questa classe è un Monitor
{
}
}
Qui il codice viene eseguito con il metodo avente la keyword synchronized e non ci sono conflitti tra i thread, infatti, il risultato è pertinente a quanto aspettato
Mentre qui è il codice con il metodo senza la keyword synchronized. Come possiamo vedere il punteggio viene aumentato anche se TOC non viene subito dopo TAC. Segno di un conflitto tra i thread
Nel caso non si vedessero le foto ecco qui una trascrizione delle suddette:
Main Thread iniziata...
<TIC> TIC:10
<TAC> TAC:10
<TOE> TOE:10 Punteggio +1
<TAC> TAC:9
<TIC> TIC:9
<TAC> TAC:8
<TOE> TOE:9 Punteggio +1
<TAC> TAC:7
<TIC> TIC:8
<TAC> TAC:6
<TOE> TOE:8 Punteggio +1
<TAC> TAC:5
<TIC> TIC:7
<TAC> TAC:4
<TOE> TOE:7 Punteggio +1
<TAC> TAC:3
<TIC> TIC:6
<TAC> TAC:2
<TOE> TOE:6 Punteggio +1
<TAC> TAC:1
<TIC> TIC:5
<TOE> TOE:5
<TIC> TIC:4
<TOE> TOE:4
<TIC> TIC:3
<TOE> TOE:3
<TIC> TIC:2
<TOE> TOE:2
<TIC> TIC:1
<TOE> TOE:1
Thread Terminati!
Punteggio: 5
Main Thread completata! tempo di esecuzione: 7030ms
BUILD SUCCESSFUL (total time: 7 seconds)
Main Thread iniziata...
<TAC> TAC:10
<TOE> TOE:10
<TIC> TIC:10
<TAC> TAC:9
<TIC> TIC:9
<TAC> TAC:8
<TOE> TOE:9 Punteggio +1
<TAC> TAC:7
<TOE> TOE:8 Punteggio +1
<TIC> TIC:8
<TAC> TAC:6
<TIC> TIC:7
<TOE> TOE:7 Punteggio +1
<TIC> TIC:6
<TAC> TAC:5
<TIC> TIC:5
<TOE> TOE:6
<TAC> TAC:4
<TIC> TIC:4
<TOE> TOE:5
<TAC> TAC:3
<TIC> TIC:3
<TAC> TAC:2
<TOE> TOE:4
<TIC> TIC:2
<TAC> TAC:1
<TOE> TOE:3 Punteggio +1
<TIC> TIC:1
<TOE> TOE:2
<TOE> TOE:1
Thread Terminati!
Punteggio: 4
Main Thread completata! tempo di esecuzione: 2809ms
BUILD SUCCESSFUL (total time: 2 seconds)
- https://manikandanmv.wordpress.com/tag/extends-thread-vs-implements-runnable/,
- http://www.simplesoft.it/java/tips/numeri-casuali-in-java.html,
- http://www.html.it/pag/15102/variabili/,
- http://lancill.blogspot.it/2012/11/annotations-override.html,
- http://www.dsi.unifi.it/~bettini/corsi/JavaConcorrente.pdf,
- StackOverflow (Domande poste dagli utenti con relativa risposta).