THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due...

44
THREAD IN JAVA Come si può realizzare il concetto di Thread in Java? NEL MODO PIÙ NATURALE! Sono oggetti particolari ai quali si richiede un servizio (chiamato start()) corrispondente al lancio di una attività, di un thread! MA: non si aspetta che il servizio termini, esso procede in concorrenza a chi lo ha richiesto! Normale Richiesta di Servizio Richiesta di Servizio start() a un Thread Flusso di Esecuzione Esecuzione del Metodo X in B B.X() return Attesa che Termini la Esecuzione di B.X() Oggetto A Oggetto B Da qui in poi ci sono due flussi di esecuzione, l'oggetto A non aspetta che termini l'esecuzione dell'oggetto B Esecuzione del Metodo run() in B B.start() Oggetto A Oggetto Thread B

Transcript of THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due...

Page 1: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

THREAD IN JAVA Come si può realizzare il concetto di Thread in Java? NEL MODO PIÙ NATURALE! Sono oggetti particolari ai quali si richiede un servizio (chiamato start()) corrispondente al lancio di una attività, di un thread! MA: non si aspetta che il servizio termini, esso procede in concorrenza a chi lo ha richiesto! Normale Richiesta di Servizio

Richiesta di Servizio start() a un Thread

Flusso diEsecuzione

Esecuzionedel Metodo

X in B

B.X()

return

Attesa cheTermini laEsecuzione

di B.X()

Oggetto A

Oggetto B

Da qui in poici sono due

flussi diesecuzione,l'oggetto Anon aspettache terminil'esecuzione

dell'oggetto B

Esecuzionedel Metodorun() in B

B.start()

Oggetto A

OggettoThread B

Page 2: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Thread Un thread (lightweight process) è un singolo flusso sequenziale di controllo all’interno di un processo

Un thread: • esegue all’interno del contesto di esecuzione di

un unico processo/programma • NON ha uno spazio di indirizzamento riservato:

tutti i thread appartenenti allo stesso processo condividono lo stesso spazio di indirizzamento

• ha execution stack e program counter privati

Page 3: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Java Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

(sottoclasse di java.lang.Object)

2. come classe che implementa l’interfaccia Runnable

1) come sottoclasse della classe Thread • Thread possiede un metodo run() che

la sottoclasse deve ridefinire • si crea un’istanza della sottoclasse

tramite new • si esegue un thread chiamando il metodo

start() che a sua volta richiama il metodo run()

Page 4: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Esempio di classe SimpleThread che è sottoclasse di Thread (modalità 1): public class SimpleThread extends Thread { public SimpleThread(String str) { super(str); } public void run() { for (int i = 0; i < 10; i++) { System.out.println(i +" "+ getName()); try { sleep((int)(Math.random()*1000)); } catch (InterruptedException e){} } System.out.println("DONE! " +getName()); } } public class TwoThreadsTest { public static void main (String[] args) { new SimpleThread("Jamaica").start(); new SimpleThread("Fiji").start(); } }

Page 5: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

E se occorre definire thread che non siano necessariamente sottoclassi di Thread? 2) come classe che implementa

l’interfaccia runnable

• implementare il metodo run() nella classe

• creare un’istanza della classe tramite new • creare un’istanza della classe Thread

con un’altra new, passando come parametro l’istanza della classe che si è creata

• invocare il metodo start() sul thread creato, producendo la chiamata al suo metodo run()

Interfaccia Runnable: maggiore flessibilità derivante dal poter essere sottoclasse di qualsiasi altra classe

Page 6: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Esempio di classe EsempioRunnable che implementa l’interfaccia Runnable ed è sottoclasse di MiaClasse (modalità 2): class EsempioRunnable extends MiaClasse

implements Runnable { // non e’ sottoclasse di Thread public void run() { for (int i=1; i<=10; i++) System.out.println(i + “ ” + i*i); } } public class Esempio { public static void main(String args[]) { EsempioRunnable e=new EsempioRunnable(); Thread t = new Thread(e); t.start(); } }

Page 7: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Il ciclo di vita di un thread

• Creato

subito dopo l’istruzione new le variabili sono state allocate e inizializzate; il thread è in attesa di passare allo stato di eseguibile

• Runnable thread è in esecuzione, o in coda d’attesa per ottenere l’utilizzo della CPU

• Not Runnable il thread non può essere messo in esecuzione dallo scheduler. Entra in questo stato quando in attesa di un’operazione di I/O, o dopo l’invocazione dei metodi suspend(), wait(), sleep()

• Dead al termine “naturale” della sua esecuzione o dopo l’invocazione del suo metodo stop() da parte di un altro thread

Page 8: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Metodi per il controllo di thread

start() fa partire l’esecuzione di un thread. La macchina virtuale Java invoca il metodo run() del thread appena creato

stop() forza la terminazione dell’esecuzione di un thread. Tutte le risorse utilizzate dal thread vengono immediatamente liberate (lock inclusi), come effetto della propagazione dell’eccezione ThreadDeath

suspend() blocca l'esecuzione di un thread in attesa di una successiva operazione di resume. Non libera le risorse impegnate dal thread (possibilità di deadlock)

resume() riprende l'esecuzione di un thread precedentemente sospeso. Se il thread riattivato ha una priorità maggiore di quello correntemente in esecuzione, avrà subito accesso alla CPU, altrimenti andrà in coda d'attesa

Page 9: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

sleep(long t) blocca per un tempo specificato (time) l'esecuzione di un thread. Nessun lock in possesso del thread viene rilasciato.

join() blocca il thread chiamante in

attesa della terminazione del thread di cui si invoca il metodo. Anche con timeout

yield() sospende l'esecuzione del thread

invocante, lasciando il controllo della CPU agli altri thread in coda d'attesa

I metodi precedenti interagiscono ovviamente con il gestore della sicurezza della macchina virtuale Java (SecurityManager, checkAccess(), checkPermission()) Altri metodi fondamentali per le operazioni di sincronizzazione fra thread Java: wait(), notify(), notifyAll()

(vedi lucido 11 e seguenti)

Page 10: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Il problema di stop()e suspend()

stop() e suspend() rappresentano azioni “brutali” sul ciclo di vita di un thread

=> rischio di determinare situazioni di blocco critico (deadlock)

Infatti: • se il thread sospeso aveva acquisito una

risorsa in maniera esclusiva, tale risorsa rimane bloccata e non è utilizzabile da altri, perché il thread sospeso non ha avuto modo di rilasciare il lock su di essa

• se il thread interrotto stava compiendo un insieme di operazioni su risorse comuni, da eseguirsi idealmente in maniera atomica, l’interruzione può condurre ad uno stato inconsistente del sistema

JDK 1.2, pur supportandoli ancora per ragioni di back-compatibility, sconsiglia l’utilizzo dei metodi stop(), suspend() e resume() (metodi deprecated)

Si consiglia invece di realizzare tutte le azioni di controllo e sincronizzazione fra thread tramite i metodi wait() e notify() su variabili condizione (astrazione di monitor)

Page 11: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Priorità dei thread in Java Scheduling: esecuzione di una molteplicità

di thread su una singola CPU, in un qualche ordine

Macchina virtuale Java (JVM) Fixed Priority Scheduling: algoritmo di scheduling molto semplice e deterministico

• JVM sceglie il thread in stato runnable con priorità più alta

• Se più thread in attesa di eseguire hanno

uguale priorità, la scelta della JVM avviene con una modalità di tipo round-robin.

La classe Thread fornisce i metodi:

• setPriority(int num) • getPriority()

con valori di num compresi fra MIN_PRIORITY e MAX_PRIORITY (costanti definite anch’esse nella classe Thread)

Page 12: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Il thread messo in esecuzione dallo scheduler viene interrotto se e solo se:

• un thread con priorità più alta diventa runnable; • il metodo run termina l’esecuzione o il thread

esegue un yield; • il quanto di tempo assegnato si è esaurito (solo su

sistemi che supportano time-slicing, come Windows95/NT/XP)

public void run() { while (tick < 200000) { tick++; if ((tick % 50000) == 0) System.out.println("Thread #" + num + ", tick = " + tick); } } Time-Sliced System Non Time-Sliced System Thread #1, tick = 50000 Thread #0, tick = 50000 Thread #0, tick = 100000 Thread #1, tick = 100000 Thread #1, tick = 150000 Thread #1, tick = 200000 Thread #0, tick = 150000 Thread #0, tick = 200000

Thread #0, tick = 50000 Thread #0, tick = 100000 Thread #0, tick = 150000 Thread #0, tick = 200000 Thread #1, tick = 50000 Thread #1, tick = 100000 Thread #1, tick = 150000 Thread #1, tick = 200000

Page 13: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

SINCRONIZZAZIONE di THREADS

Quando due o più thread eseguono concorrentemente, è in generale impossibile prevedere l'ordine in cui le loro istruzioni verranno eseguite.

Problemi nel caso in cui i thread invocano metodi sullo stesso oggetto di cui condividono il riferimento. SONO POSSIBILI INCONSISTENZE! Esempio: Oggetto conto_corrente con metodo versamento(importo)

public void versamento(int importo) { int nuovo_totale; //variabile locale al metodo nuovo_totale = totale_conto + importo - tasse;

//totale_conto è una variabile dell'oggetto //e indica i soldi totali sul conto //l'istruzione calcola il nuovo totale del //conto corrente mettendo il risultato nella //variabile locale

totale_conto = nuovo_totale; //metto il totale calcolato nella variabile //dell'oggetto che memorizza il conto totale } Supponiamo che due thread abbiano entrambi un riferimento all'oggetto e invochino separatamente il metodo versamento per fare ognuno un versamento sul conto…

Page 14: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

ESEMPIO DI INCONSISTENZA Supponiamo che l'oggetto conto_corrente abbia nella sua variabile totale_conto il valore 2000. Ci si aspetta che se qualcuno deposita 1000 e qualcun altro deposita 500, e supponendo che le tasse per ogni versamento siano 100, alla fine la variabile totale_conto valga 3300. Supponiamo che questi due depositi vengano fatti in concorrenza da due thread diversi e che durante l'esecuzione i thread si alternino sul processore, come segue Thread A Thread B conto_corrente.versamento(1000) // il thread invoca il metodo e il flusso di esecuzione fa a eseguire le istruzioni di tale metodo nuovo_totale= totale_conto+importo-tasse; //nuovo_totale = 2000+1000-100 totale_conto = nuovo_totale; //totale_conto vale 2900

conto_corrente.versamento(500) // il thread invoca il metodo e il flusso di esecuzione fa a eseguire le istruzioni di tale metodo nuovo_totale= totale_conto+importo-tasse; //nuovo_totale = 2000+500-100 totale_conto = nuovo_totale; //totale_conto vale 2400

Alla fine, totale_conto vale 2400!!!! Il metodo versamento() non è atomico

Page 15: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Mutua Esclusione Differenti thread che fanno parte della stessa applicazione Java condividono lo stesso spazio di memoria

è possibile che più thread accedano contemporaneamente allo stesso metodo o alla stessa sezione di codice di un oggetto

Servono meccanismi di mutua esclusione sull’accesso al metodo e alla sezione di

codice condivisa JVM supporta la mutua esclusione nell’accesso a risorse condivise tramite la keyword synchronized synchronized su:

• singolo metodo, o • blocco di istruzioni

Page 16: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

In pratica: a ogni oggetto Java è automaticamente

associato un lock per accedere a un metodo o una sezione synchronized, un thread deve prima acquisire il lock dell’oggetto

il lock è automaticamente rilasciato quando il thread esce dalla sezione synchronized, o se viene interrotto da un’eccezione

un thread che non riesce ad acquisire un lock rimane sospeso sulla richiesta della risorsa fino a che il lock non è disponibile

Quando un thread in esecuzione tenta di accedere ad una sezione synchronized di un oggetto il cui lock è già stato acquisito da un altro thread, che esegue concorrentemente, esso si mette in attesa di poter acquisire il lock. Il thread rimane in stato runnable. Appena il lock è rilasciato, lo scheduler può potenzialmente mettere in esecuzione il thread. NOTA:

ad ogni oggetto contenente metodi o blocchi synchronized viene assegnata una sola variabile condizione

Due thread non possono accedere contempo-raneamente a due sezioni synchronized diverse di uno stesso oggetto

Page 17: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

ESEMPIO – Lettura Dati da Socket .... public class AsyncReadSocket extends Thread { private Socket s; private StringBuffer result;

public AsyncReadSocket(Socket s) { this.s = s; result = new StringBuffer(); }

public void run() { DataInputStream is = null; try { is=new DataInputStream(s.getInputStream()); } catch (Exception e) {}

while (true) { try { char c = is.readChar(); result.append(c); } catch (Exception e) {} }

}

public String getResult() { //metodo usato da un altro thread per leggere

//la stringa letta dalla socket String retval = result.toString(); result = new StringBuffer(); return retval;

} }

Page 18: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Problemi: 1. che cosa accade se un thread chiama il

metodo getResult() nello stesso momento in cui il metodo readChar() ritorna con i dati?

Esempio di sequenza e di corsa critica: Thread A

1. getResult()

2. String retval=result.toString();

5. result=new StringBuffer()

Thread AsynchReadSocket

3. Ritorno da readChar()

4. result.append(c)

I dati appesi a result al passo 4 vanno persi; non sono stati, infatti ritrovati dal Thread A al passo 2 e al passo 5 Thread A assegna result ad un nuovo StringBuffer (vuoto) Un caso di corsa critica si verifica nell’accesso a result in due casi: 1. Thread A chiama getResult() e Thread AsynchReadSocket esegue is.readChar e result.append

2. due thread chiamano contemporaneamente getResult()

Page 19: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

ESEMPIO MODIFICATO .... public class AsyncReadSocket extends Thread { private Socket s; private StringBuffer result;

public AsyncReadSocket(Socket s) {

this.s = s; result = new StringBuffer(); } public void run() {

DataInputStream is = null; try {

is=new DataInputStream(s.getInputStream()); }catch(Exception e){} while (true)

{ try{ char c = is.readChar();

appendResult(c); }catch(Exception e){}

} } public synchronized String getResult() { String retval = result.toString(); Result = new StringBuffer(); return retval; } public synchronized void appendResult(char c) { result.append(c); }}

Page 20: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

1. se avessimo definito come sychronized solo getResult() e non appendResult()? => non avremmo risolto i problemi di corsa critica

2. e se definissimo run() come metodo synchronized?

La mutua esclusione durerebbe all’infinito e due Thread di tipo AsynchReadSocket non potrebbero eseguire concorrentemente

Page 21: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

ESEMPIO - Uno stack sincronizzato class EmptyStackException extends Exception { EmptyStackException() {} EmptyStackException(String s) { super(s); } }

class Stack { Object val[]; int sp = 0; Stack(int max) { val = new Object[max]; } public synchronized void push(Object e) { val[sp++]=e; } public synchronized Object pop() throws EmptyStackException { if (sp>0) return val[--sp]; else throw new EmptyStackException(); } public boolean isEmpty() { return (sp==0); } public void print() { System.out.print("Stack content: ["); for(int i=0; i<sp-1; i++) System.out.print(val[i] + ", "); if (sp>0) System.out.print(val[sp-1]); System.out.print("]"); } }

Page 22: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

ESEMPIO - Uso dello stack Questo codice riunisce in sé tre attività diverse: il pusher (che inserisce nello stack dato 20 oggetti Integer), il popper (che li estrae), e il printer (che visualizza lo stato dello stack). Ogni istanza di MyBody9 si comporterà quindi o come pusher, o come popper, o come printer, a seconda del nome che le viene passato all’atto della costruzione. class MyBody9 implements Runnable { String chiSonoIo; static Stack s = new Stack(100); // condiviso MyBody9(String name){ chiSonoIo = name; } public void run(){ if (chiSonoIo.equals("pusher")) { for(int i=0; i<20; i++) { System.out.println( Thread.currentThread().getName() + " pushing " + i); s.push( new Integer(i) ); } } else if (chiSonoIo.equals("popper")) { try { Thread.sleep(500); } catch (InterruptedException ee) {} try { while(!(s.isEmpty())) System.out.println( Thread.currentThread().getName() + " popping " + s.pop()); } catch (EmptyStackException e) { System.out.println( Thread.currentThread().getName() + " tried to pop an empty stack"); } } else /* sono il printer */ s.print();}}

Page 23: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

ESEMPIO - Uso dello stack public class Esempio9 { public static void main(String args[]){ // creo tre istanze configurate diversamente MyBody9 b1 = new MyBody9("pusher"); MyBody9 b2 = new MyBody9("popper"); MyBody9 b3 = new MyBody9("printer"); // creo tre thread, uno per task da eseguire Thread t1 = new Thread(b1, "Produttore"); Thread t2 = new Thread(b2, "Consumatore"); Thread t3 = new Thread(b3, "Visualizzatore"); // ora attivo Produttore e Consumatore… t2.start(); t1.start(); // …e aspetto che finiscano entrambi try { t1.join(); } catch(InterruptedException e1) {} try { t2.join(); } catch(InterruptedException e2) {} // alla fine attivo il Visualizzatore t3.start(); } }

NOTA: • a rigore, esiste ancora un rischio di inconsistenza sugli

oggetti memorizzati nello stack, in quanto si memorizzano riferimenti: altri thread potrebbero fare riferimento agli stessi oggetti, e modificarli mentre sono memorizzati nello stack

• sarebbe più sicuro archiviare nello stack delle copie (cloni) degli oggetti anziché mantenere dei riferimenti

Page 24: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Esempio (Lettori - Scrittori)

Classe che modella un DB (l’implementazione è semplice: il DB è rappresentato da un intero) class Rwbasic { protected int data=0; //the database

public void read() {

System.out.println(“read: “ +data); } public void write() {

data++; System.out.println(“wrote: “ +data);

} }

Page 25: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

class Reader extends Thread { int rounds; Rwbasic rw;

public Reader(int rounds, Rwbasic rw) {

this.rounds=rounds; this.rw=rw; } public void run() {

for (int i= 0; i< rounds; i++){rw.read();} }

} class Writer extends Thread { int rounds; Rwbasic rw;

public Writer(int rounds, Rwbasic rw) {

this.rounds=rounds; this.rw=rw; } public void run() {

for(int i= 0; i< rounds; i++){rw.write();} }

} class Main { static Rwbasic rw = new Rwbasic(); public static void main(String[] args)

{ int rounds = Integer.parseInt(args[0], 10); new Reader(rounds, rw).start(); new Writer(rounds, rw).start();

} } => accesso concorrente alla variabile data!

Page 26: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Esempio Produttori e Consumatori in Mutua Esclusione class Rwbasicexclusive extends Rwbasic {

public synchronized read() {

System.out.println(“read: “ +data); } public synchronized void write() {

data++; System.out.println(“wrote: “ +data);

} class Reader extends Thread {

int rounds; Rwbasicexclusive rw;

public Reader(int rounds, Rwbasicexclusive rw) { this.rounds=rounds; this.rw=rw; } public void run() {

for (int i= 0; i< rounds; i++) {

rw.read(); }

} }

Page 27: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

class Writer extends Thread {

int rounds; Rwbasicexclusive rw;

public Writer(int rounds, Rwbasicexclusive rw) {

this.rounds=rounds; this.rw=rw;

} public void run() {

for (int i= 0; i< rounds; i++) {

rw.write(); }

} } class Main {

static Rwbasicexclusive rw; rw=new Rwbasicexclusive();

public static void main(String[] args) { int rounds = Integer.parseInt(args[0],10); new Reader(rounds, rw).start(); new Writer(rounds, rw).start(); }

}

Page 28: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Sincronizzazione Mutua esclusione uso di lock (tramite keyword synchronized) Politiche di sincronizzazione nell’accesso ad una risorsa condivisa uso di meccanismi di guardia e di variabili logiche di condizioni (tramite keyword synchronized e metodi wait() e notify())

Page 29: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Esempio (Controllore per un parcheggio di auto)

Progettare un sistema di controllo per l’ingresso ad un parcheggio per auto. Il sistema deve permettere alle auto di entrare solo quando il parcheggio non è pieno e non permette alle auto di uscire quando non ci sono auto nel parcheggio. Simulare l’arrivo e l’uscita di auto tramite threads.

Threads: Arrivals, Departures (implementano Runnable). CarParkControl è la classe che fornisce il controllo nell’accesso al parcheggio.

ARRIVALS CARPARKCONTROL

DEPARTURESarrive depart

CARPARKARRIVALS CARPARK

CONTROLDEPARTURESarrive departARRIVALS CARPARK

CONTROLDEPARTURESarrive depart

CARPARK

Page 30: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

class CarParkControl { protected int spaces; protected int capacity; CarParkControl(int capacity) {capacity = spaces = n;}

synchronized void arrive() { … --spaces; … } synchronized void depart() { … ++spaces; … } } class Arrivals implements Runnable { CarParkControl carpark; Arrivals(CarParkControl c) {carpark = c;} public void run() { try { while(true) { carpark.arrive(); } } catch (InterruptedException e){} } }

Page 31: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

e le variabili condizione? class CarParkControl { protected int spaces; protected int capacity; CarParkControl(int capacity) {capacity = spaces = n;} synchronized void arrive()

throws InterruptedException { while (spaces==0) wait(); --spaces; notify(); } synchronized void depart()

throws InterruptedException { while (spaces==capacity) wait(); ++spaces; notify(); } }

Page 32: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Ogni oggetto Java (istanza di una sottoclasse qualsiasi della classe Object) fornisce i metodi di sincronizzazione:

• wait() blocca l’esecuzione del thread invocante in attesa che un altro thread invochi i metodi notify() o notifyAll() per quell’oggetto. Il thread invocante deve essere in possesso del lock sull’oggetto (ossia la wait() deve essere eseguita all’interno di un metodo o sezione synchronized) altrimenti viene lanciata un’eccezione; il suo blocco avviene in maniera atomica dopo aver rilasciato il lock. Il thread riacquisisce il lock prima di ritornare dal metodo wait() per evitare situazioni di corsa critica. Anche varianti con specifica di timeout Quando un thread esegue una wait() si porta in stato Not-Runnable non può essere messo in esecuzione dallo scheduler

• notify() risveglia un unico thread in attesa sul monitor dell’oggetto in questione. Ciò significa che il thread risvegliato viene portato in stato Runnable e può quindi essere messo in esecuzione dallo scheduler.

Page 33: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Se più thread sono in attesa, la scelta avviene in maniera arbitraria, dipendente dall’implementazione della macchina virtuale Java. Il thread risvegliato compete con ogni altro thread, come di norma, per ottenere la risorsa protetta. Anche la notify deve essere chiamata all’interno di un metodo synchronised.

• notifyAll() esattamente come notify(), ma risveglia tutti i thread in attesa per l’oggetto in questione. È necessario tutte le volte in cui più thread possono essere sospesi su differenti sezioni critiche dello stesso oggetto (unica coda d’attesa)

Per ogni oggetto sincronizzato esiste un’unica coda di attesa (wait-queue). Nell’es. precedente, il parcheggio è l’oggetto sincronizzato ed esiste un’unica coda di attesa sia per i thread rappresentativi delle auto in ingresso sia per quelle in uscita al parcheggio.

condizioni di sospensione diverse. E se per testare la condizione di sospensione avessimo utilizzato if(condizione)wait()?

Page 34: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Altro Esempio: g i n J a v a public class GuardedBoundedBuffer implements Buffer { private List data; private final int capacity; public GuardedBoundedBuffer(int capacity) { data = new ArrayList(capacity); this.capacity = capacity; } public synchronized Object take() throws Failure {

while (data.size() == 0) try { wait(); } catch(InterruptedException ex) {throw new

Failure();} Object temp = data.get(0); data.remove(0); notifyAll(); return temp;

} public synchronized void put(Object obj) throws Failure {

while (data.size() == capacity) try { wait(); } catch(InterruptedException ex)

{throw new Failure();} data.add(obj); notifyAll();}

public synchronized int size() {

return data.size(); } public int capacity() { return capacity; } }

Page 35: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Potrebbero wait() e notify() essere usati in sostituzione dei metodi synchronized? NO!!! Se non vengono chiamati dentro metodi synchronized si verificano possibili corse critiche. Wait() e notify() rilasciano i lock, ma non li acquisiscono! Esempio di corsa critica in presenza di due thread:

1. il primo thread testa una condizione e verifica che la condizione non è verificata

2. il secondo thread modifica la condizione rendendola vera per il primo thread

3. il secondo thread chiama una notify() che pero’ non produce alcun effetto sul primo thread perché non ha ancora chiamato la wait()

4. il primo thread a questo punto chiama la wait() e si sospende all’infinito

=> occorrono i lock di sincronizzazione. La wait è realizzata in modo da rilasciare il lock prima di mettersi in attesa e di riacquisirlo (perché all’interno di una sezione synchronised) prima di ritornare dalla wait

Principale limitazione meccanismi Java: unica wait-queue per un oggetto sincronizzato, quindi difficoltà di sospendere thread su differenti code, mancanza dei semafori privati.

Page 36: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Produttori e Consumatori Concorrenti Possibili Politiche di Accesso • se ci sono dei lettori già attivi sulla risorsa, può un altro

thread lettore accedere anche se c’è già uno scrittore sospeso?

• se sulla risorsa è attivo uno scrittore e in attesa ci sono lettori e scrittori, a chi dare la precedenza? Ai lettori? Agli scrittori? Al primo che ha fatto la richiesta? Random? In maniera alternata?

Esempio: class ReadersWriters extends Rwbasic {

private int nr=0;

private synchronized void startRead() {

nr++; }

public synchronized void endRead() { nr--; if (nr==0) notify();

// sveglia i Writers in attesa }

// il metodo read non è più synchronized public void read() {

startRead(); System.out.println(“wrote “ +data);

endRead(); }

Page 37: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

public synchronized void write() {

while (nr>0) //finché ci sono dei readers try { wait();}

catch (InterruptedException ex) {return;} data++;

System.out.println(“wrote “ +data); notify; /*necessaria per gli altri writers

in attesa */ }

} class Reader extends Thread { int rounds; ReadersWriters rw;

public Reader(int rounds, ReadersWriters rw) {

this.rounds=rounds; this.rw=rw; } public void run() {

for (int i=0; i< rounds; i++) {rw.read();} }

} class Writer extends Thread { int rounds; ReadersWriters rw;

public Writer(int rounds, ReadersWriters rw) {

this.rounds=rounds; this.rw=rw; } public void run() {

for (int i=0; i<rounds; i++) {rw.write();} }

}

Page 38: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

class Main {

static ReadersWriters rw; rw=new ReadersWriters ();

public static void main(String[] args) {

int rounds=Integer.parseInt(args[0], 10); new Reader(rounds, rw).start();

new Writer(rounds, rw).start(); }

}

A chi viene data la priorità nell’accesso alla risorsa condivisa (variabile data)?

Page 39: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Variante: public abstract class RW {

protected int activeReaders = 0; protected int activeWriters = 0; protected int waitingReaders = 0; protected int waitingWriters = 0; public void read() {

beforeRead(); try { doRead(); } finally { afterRead(); }

}

public void write() {

beforeWrite(); try {doWrite(); } finally { afterWrite();}

}

protected boolean allowReader() {

return waitingWriters == 0 && activeWriters == 0;

}

protected boolean allowWriter() {

return activeReaders == 0 && activeWriters == 0;

}

Page 40: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

a n g i n J a v a protected synchronized void beforeRead() {

++waitingReaders; while (!allowReader()) try { wait(); } catch (InterruptedException ex) { ... } --waitingReaders; ++activeReaders;

}

protected abstract void doRead(); protected synchronized void afterRead() {

--activeReaders; notifyAll();

} C o n c u r t P r o g r a n J a v a

protected synchronized void beforeWrite() {

++waitingWriters; while (!allowWriter()) try { wait(); } catch (InterruptedException ex) { ... } --waitingWriters; ++activeWriters;

}

protected abstract void doWrite();

protected synchronized void afterWrite() {

--activeWriters; notifyAll();

} }

Page 41: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Semafori in Java Utilizzando i costrutti primitivi di Java si possono realizzare meccanismi più sofisticati (semafori e anche monitor). public class Semaphore {

private int value; public Semaphore (int initial){

value = initial; } synchronized public void signal()

++value; notify();

} synchronized public void wait()

throws InterruptedException { while (value== 0) wait(); --value;

} }

I metodi wait e signal sono a volte chiamati down e up.

Page 42: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Produttore/Consumatore con Semafori Java

class SemaBuffer implements Buffer {

protected Object[] buf; protected int in = 0; protected int out = 0; protected int count = 0; protected int size = 0;

Semaphore full; //counts number of items Semaphore empty; //counts number of spaces SemaBuffer(int size) {

this.size=size; buf=new Object[size]; full = new Semaphore(0); empty= new Semaphore(size);

}} synchronized public void put(Object o)

throws InterruptedException { empty.wait(); buf[in] = o; ++count; in=(in+1)%size; full.signal();

} synchronized public Object get()

throws InterruptedException{ full.wait(); Object o =buf[out]; buf[out]=null; --count; out=(out+1)%size; empty.signal(); return (o);

} Funziona tutto bene???

Page 43: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Produttore/Consumatore con Semafori Java

L’esempio sopra non garantisce liveness, perché ci può essere deadlock: • se il buffer è vuoto • consumatore fa una get • consumatore si blocca e rilascia il lock sul

semaforo • produttore invoca put ma viene sospeso sul

lock del buffer, che non è stato rilasciato dal consumatore.

In questo caso il deadlock è causato da chiamate innestate (nested monitor).

Page 44: THREAD IN JAVA - unibo.itlia.deis.unibo.it/Courses/SistOpLS0304/Sincronizzazione.pdfJava Thread Due modalità per implementare thread in Java: 1. come sottoclasse della classe Thread

Esempio corretto di Produttore/Consumatore con

semafori Java public void put(Object o)

throws InterruptedException { empty.wait(); synchronized(this){

buf[in] = o; ++count; in=(in+1)%size; } full.signal();

}

Il deadlock può essere evitato solo attraverso un progetto attento, sincronizzando la regione critica di accesso al buffer solo dopo il passaggio del semaforo Quindi la realizzazione di costrutti di sincronizzazione di alto livello in Java richiede particolare attenzione. Si possono anche realizzare variabili condizioni e monitor come nella definizione di Hoare.