Threads in Java Prof.ssa Sara Michelangeli I threads I threads o "processi leggeri" (light weight process) sono flussi di esecuzione nell'ambito di uno stesso processo, che condividono tra loro risorse e aree di memoria, ad eccezione del proprio program counter e del proprio stack. Con multithreading definiamo l esecuzione contemporanea (o apparentemente contemporanea) di diversi thread nell ambito dello stesso processo. La gestione del multithreading può essere a carico del processo stesso, oppure del sistema operativo (se supporta il multithreading). La stessa esecuzione di un semplice programma a carico di JVM dà origine a un thread. La JVM in runtime normalmente usa i servizi di sistema operativo relativi ai thread (modalità native thread o kernel thread), come avviene in genere in ambiente Windows e Solaris (solitamente in time-slicing e scheduling preemptive in presenza di priorità) ma può anche prendere interamente in carico la schedulazione dei thread (modalità green-thread o user thread), come avviene in alcune versioni di Unix. In tal caso il sistema operativo assegna alla JVM un quanto di tempo, indipendentemente dal numero di thread attivi, la cui esistenza è totalmente ignorata dal sistema stesso, che vede solo il thread principale. In tal caso è dunque la JVM che si occupa della gestione dei propri thread. E' buona regola verificare la correttezza dell'esecuzione in più ambienti e su varie piattaforme e privilegiare delle modalità operative in cui il rilascio delle risorse, quando opportuno, sia gestito a livello di programmazione. Java supporta il multithreading in modo nativo attraverso la classe java.lang.thread e l interfaccia java.lang.runnable. Per il programmatore un Thread è un flusso di esecuzione individuato da un'istanza della classe Thread (o di una sua sottoclasse derivata) o da un oggetto che implementa l interfaccia Runnable. Un thread esegue l' algoritmo codificato nel metodo run() e va in esecuzione concorrente con gli altri thread esistenti. Lo stesso metodo main() di un qualunque oggetto attiva un thread, l'entry point dell'esecuzione, che termina al terminare del main stesso e viene schedulato con tutti gli altri thread definiti nel progetto. Come già detto Java mette a disposizione la classe Thread fornita dal package java.lang. Esamineremo prima suoi i metodi principali per poi passare a spiegare le modalità di implementazione. Costruttori Tra i vari costruttori disponibili esaminiamo i più utilizzati public Thread(); dichiara un oggetto di tipo thread public Thread(String name); dichiara un oggetto di tipo thread definendone il nome per implementare l'interfaccia Runnable nel caso si attui la seconda modalità di programmazione si useranno public Thread(Runnable target); public Thread(Runnable target, String name);
Metodi statici I metodi statici agiscono sul thread corrente. Ricordiamo: public static void sleep(long millisecondi) interrompe il thread corrente che viene meso in attesa per i millisecondi passati nel parametro, l'esecuzione passa al primo thread della reading list, con cambio di contesto e forzatura della schedulazione. Tutti gli altri thread continuano ad avanzare nell'esecuzione. In caso di sincronizzazione bisogna tener presente che lo sleep non agisce sui lock del thread. Se in questo tempo qualcun altro chiama il suo metodo interrupt(), il thread viene riattivato da una InterruptedException. public static void yield() il thread corrente cede forzatamente la Cpu ad altri thread in attesa, di pari priorità. Se nessun thread di pari priorità è in attesa il thread continua l'avanzamento senza cambio di contesto.questo metodo è particolarmente utile per forzare il rilascio di Thread cpu_bound, senza dipendere dal sistema operativo. Metodi di istanza I metodi di istanza vengono richiamati su un determinato thread. I metodi più importanti sono public void run() E' il metodo più importante, che attua un override sul metodo run di classe base, che è vuoto. Nel metodo run viene implementato il codice che il thread deve effettivamente eseguire. Definisce dunque il suo comportamento e la sequenza delle istruzioni che andranno in esecuzione all'attivazione del thread. Public synchronized void start() E' il metodo con cui un trhead viene avviato e posto in modalità ready to run. La chiamata di start su un thread già avviato genera un errore. public void stop() (deprecated) Termina un thread public void join() Mette in attesa il thread corrente fino alla terminazione del thread su cui è chiamato. E' possibile impostare un'attesa massima passano i millisecondi come parametro. public void suspend() (deprecated) Sospende un thread, mentre gli altri continuano l'esecuzione, ma non libera le risorse impegante public void resume() (deprecated) E' il metodo utilizzato per riattivare il thread e renderlo nuovamente eseguibile,trasferendolo
nella reading list. I metodi stop, suspend e resume sono deprecati in quanto, agendo forzatamente su un thread senza alcun controllo sul suo stato di esecuzione possono creare situazioni stallo o deadlock 1)Prima modalità di implementazione di un thread Creazione di una sottoclasse Thread con extends Thread Il primo esercizio crea una sottoclasse di Thread chiamata conta che stampa a console per 10 volte un indice che si incrementa ad ogni step e il nome del Thread class conta extends Thread{ setname("conta"); /* il thread corrente è chiamato conta*/ for (int i=0; i<10; i++) System.out.println(i+" "+getname()); public class thread1 { conta threadconta=new conta (); threadconta.start(); /* il thread corrente è il main*/ System.out.println(Thread.currentThread().getName()); Questa è l'esecuzione main 0 conta 1 conta 2 conta 3 conta 4 conta 5 conta 6 conta 7 conta 8 conta 9 conta Modifichiamo questo esercizio: istanziamo due thread che avranno un nome diverso, acquisito come parametro passato dal costruttore di classe e mandiamoli in esecuzione in concorrenza. Usiamo una sleep nella for per rallentare i thread favorendone l'alternanza in run class conta2 extends Thread{ public conta2(string nome) { setname(nome);
for (int i=0; i<10; i++){ try { sleep(2); catch (InterruptedException e) { System.out.println(i+" "+getname()); public class thread2 { conta2 threadconta1=new conta2 ("conta1"); conta2 threadconta2=new conta2 ("conta2"); threadconta1.start(); threadconta2.start(); System.out.println(Thread.currentThread().getName()); L'esecuzione sarà del tipo main 0 conta1 1 conta1 0 conta2 2 conta1 1 conta2 3 conta1 4 conta1 5 conta1 2 conta2 6 conta1 7 conta1 3 conta2 8 conta1 4 conta2 9 conta1 5 conta2 6 conta2 7 conta2 8 conta2 9 conta2 2)Seconda modalità di implementazione di un thread Utilizzo della interfaccia Runnable con implements Runnable L'utilizzo dell'interfaccia Runnable per la gestione dei Threads è molto importante soprattutto quando la nostra classe deve già ereditare per esempio da una classe Applet, o un JComponent. Sappiamo infatti che in Java l'erediterietà multipla non è consentita, quindi, attraverso l'interfaccia Runnable possiamo utilizzare la classe base senza ereditarla. Gli steps da eseguire per creare un thread con questa modalità sono: creare una classe implements Runnable fare un override del metodo run all'interno di questa classe istanziare un oggetto di questa classe Runnable creare un oggetto di classe thread istanziando come parametro nel suo costruttore l'oggetto Runnable appena creato avviare l'oggetto thread così istanziato, richiamando su di esso il metodo start.
Vediamo per una maggiore chiarezza l'esempio precedente implementato con questa seconda modalità. class contarunnablethread implements Runnable{ public void run() { for(int i=0;i<10;i++) System.out.print(i+""); public class contarunnable { contarunnablethread ct=new contarunnablethread(); Thread t=new Thread(ct); t.start(); Uno tra i primi classici esempi su queste prooblematiche è l'esercizio delle cosiddette "campane stonate", con tre thread che si alternano in esecuzione con la stampa di un proprio suono, passato come parametro, che sarà "din", "don", o "dan" class campana implements Runnable{ String suono; int volte; public campana(string s,int v) { this.suono=s; this.volte=v; for (int i=0;i<volte;i++) { System.out.print(suono+" "); try{ Thread.sleep(100); catch(interruptedexception e){ public class runnablethread { campana cdin=new campana("din",5); Thread tdin=new Thread(cdin); campana cdon=new campana("don",9); Thread tdon=new Thread(cdon); Thread tdan=new Thread(new campana("dan",13)); tdin.start(); tdon.start(); tdan.start(); l'esecuzione sarà simile a don din dan din dan don din dan don din dan don din dan don dan don dan don dan don dan don dan dan dan dan