Sei sulla pagina 1di 32

Esercitazioni

Ingegneria del So2ware 8 Threads


Giordano Tamburrelli tamburrelli@elet.polimi.it h@p://giordano.webfacDonal.com

Flusso (thread) di esecuzione


In un programma sequenziale esiste un solo usso di esecuzione aJvo In un programma concorrente esistono pi ussi di esecuzione aJvi (task) Scopo?
Applicazioni interaJve Server

Thread vs Processi:
Thread sono pi leggeri dei processi Sfru@ano una comunicazione a memoria condivisa

Thread in Javametodo Thread


1. Denire una classe che eredita da Thread e conDene il metodo run()
class ListSorter extends Thread { public void run() { gli statements del thread che si vuol denire } } ListSorter concSorter = new ListSorter(list1) concSorter.start();

2. Creare istanza della classe

3. Invocare il metodo start(), che a sua volta chiama run()


lancia il thread e ritorna immediatamente
3

Thread in Javametodo Runnable


1. Denire una classe che implementa l'interfaccia Runnable, eventualmente estende altra classe e che conDene il metodo run()
class ListSorter implements Runnable { public void run() { gli statements del thread che si vuol denire } } ListSorter concSorter = new ListSorter(list1); Thread myThread = new Thread(concSorter); myThread.start()

2. Creare istanza 3. Creare thread

4. AJvare metodo run, a@raverso metodo start()

Quale metodo uDlizzare?

Metodo Thread
Vantaggi: pi intuiDvo, perme@e di istanziare un solo ogge@o. Svantaggi: poco essibile.

Metodo Runnable
Vantaggi: perme@e di avviare classi che estendono altre classi come Thread. Vedremo pi avanD che esistono meccanismi pi avanzaD per la schedulazione di Thread che preferiscono oggeJ di Dpo Runnable. Svantaggi: un metodo pi complicato.

Non-Determinismo
class MyThread implements Runnable {! private String message;! public MyThread(String m) {! message = m;! }! public void run() {! for (int r = 0; r < 90000; r++)! System.out.println(message);! }! }! class ProvaThread {! public static void main(String[] args) {! Thread t1, t2;! MyThread r1, r2;! r1 = new MyThread("primo thread");! r2 = new MyThread("secondo thread");! t1 = new Thread(r1);! t2 = new Thread(r2);! t1.start();! t2.start();! }! }! Quale sar loutput?

DaD condivisi
Pu essere necessario imporre che certe sequenze di operazioni che accedono a daD condivisi vengano eseguite dai task in mutua esclusione
class ContoCorrente { private oat saldo; public ContoCorrente (oat saldoIniz) { saldo = saldoIniz; } public void deposito (oat soldi) { saldo += soldi; } public void prelievo (oat soldi) { saldo -= soldi; } }

che succede se due Thread concorrenD cercano l'uno di depositare e l'altro di prelevare?

Operazioni non atomiche


Saldo iniziale = 100 Thread 1 deposito(50) read(saldo) -> 100 read(saldo) -> 100 sum(50+100) -> 150 sub(100-50) -> 50 write(saldo) -> 50 write(saldo) -> 150 Saldo nale = 150 Thread 2 prelievo(50)

Operazioni atomiche
Saldo iniziale = 100 Thread 1 deposito(50) read(saldo) -> 100 sum(50+100) -> 150 write(saldo) -> 150 read(saldo) -> 150 sub(150-50) -> 100 write(saldo) -> 100 Saldo nale = 100 Thread 2 prelievo(50)

Come rendere i metodi "atomici"


La parola chiave "synchronized"

class ContoCorrente { private oat saldo; public ContoCorrente (oat saldoIniz) { saldo = saldoIniz; } public synchronized void deposito (oat soldi) { saldo += soldi; } public synchronized void prelievo (oat soldi) { saldo -= soldi; } }

10

Metodi synchronized

Java associa un lock (monitor) a ciascun ogge@o


Solo un thread alla volta pu eseguire il codice dellogge@o

Quando il metodo synchronized viene invocato


se nessun metodo synchronized in esecuzione, l'ogge@o viene bloccato (locked) e quindi il metodo viene eseguito se l'ogge@o bloccato, il task chiamante viene sospeso e messo in coda no a quando il task bloccante libera il lock

11

Metodi synchronized (2)

Quando un metodo synchronized viene invocato da un altro metodo synchronized appartenente al medesimo ogge@o, il thread chiamante non deve competere per il monitor, in quanto questulDmo gi stato acquisito durante linvocazione del primo metodo (reentrant lock) Laccesso mutuamente esclusivo vale solo per i metodi dichiaraD synchronized: laccesso a@raverso gli altri metodi non mutuamente esclusivo, cio pu avvenire anche mentre un thread ha acquisito il monitor

12

Precondizioni per metodi synchronized


Come evitare il prelievo se il conto va "in rosso"?

class ContoCorrente { private oat saldo; synchronized public void prelievo (oat soldi) { while (saldo-soldi<0) wait(); saldo -= soldi; } } rilascia il lock sull'ogge@o e sospende il task

13

Come risvegliare un task in wait?


class ContoCorrente { private oat saldo; public ContoCorrente (oat saldoIniz) { saldo = saldoIniz; } synchronized public void deposito (oat soldi) { saldo += soldi; risveglia un task sospeso in no?fy(); wait, se esiste nondeterminismo } synchronized public void prelievo (oat soldi) { while (saldo-soldi<0) wait(); saldo -= soldi; A@enzione! } if (saldo-soldi<0) wait(); Non suciente
}

14

PrimiDve di sincronizzazione
Le primiDve di sincronizzazione wait, no?fy e no?fyAll sono associate a ogni ogge@o, in quanto denite nella classe Object. Consentono a un thread di sospendersi allinterno di un monitor (wait), e di risvegliare uno (no?fy) o tuJ (no?fyAll) i thread sospesi. Tali primiDve operano sul monitor associato allogge@o, pertanto possono essere invocate allinterno di un thread solo dopo che esso ha acquisito il monitor: Cio solo se il thread sta eseguento allinterno di un blocco o metodo synchronized Se si invoca una di queste primiDve su un ogge@o per cui non si acquisito il monitor si oJene una IllegalMonitorStateExcep?on

Il blocco synchronized!
Talvolta risulta necessario controllare laccesso concorrente a porzioni di codice con una granularit pi ne del metodo Pi grande la porzione di codice sincronizzata, minore il parallelismo In quesD casi possibile impiegare il blocco synchronized synchronized (obj) { ... codice critico ... }" La semanDca del blocco synchronized simile a quella dei metodi synchronized, con la dierenza che il monitor viene acquisito sullogge@o obj anzich su this. void m() { synchronized(this) { ... codice critico ...! }! }!

void synchronized m() { ... codice critico ...! }!

Esempio: una coda fo condivisa


Operazione di inserimento di elemento:
sospende task se coda piena
while (codaPiena()) wait();

al termine
noDfy();

Operazione di estrazione di elemento:


sospende task se coda vuota
while (codaVuota()) wait();

al termine
noDfy();

invece noDfyAll risveglia tuJ MA uno solo guadagna il lock

17

Priorit e scheduling
Assegnabile priorit (1-10) ai task (setPriority); default 5 Alcune pia@aforme supportano il "Dme slicing" In assenza, un thread viene eseguito no al completamento, a meno che non divenD "blocked", "waiDng" o "dead" Compito dello scheduler mandare in esecuzione il thread con priorit pi alta

18

Alcuni metodi della classe Thread


void start() Avvia il thread (eseguendo il metodo run()) void join() Aspe@a no a quando il thread non termina boolean isAlive() Controlla se il thread in esecuzione sta?c void sleep(int ms) Aspe@a ms millisecondi sta?c boolean holdsLock(Object obj) ResDtuisce true se il thread corrente ha acquisito un lock del monitor di obj sta?c void yield() Sospende temporaneamente il thread corrente per perme@ere lesecuzione degli altri thread in esecuzione

Ciclo di vita di un thread


born start noDfy noDfyAll ready ne I/O ne lock running I/O lock blocked

wait waiDng

dead

20

Esecutori

Sono una famiglia di classi che implementano linterfaccia ExecutorService Si uDlizzano come metodo alternaDvo per eseguire delle classi che implementano Runnable (denite Task)
void submit(Runnable task) Schedula lesecuzione del task void shutdown() Impedisce allesecutore di acceNare nuovi task boolean awaitTermina?on(long ?meout, TimeUnit unit) A@ende la ne dellesecuzione di tuJ i task oppure il tempo specicato come Dmeout ...

Esempio uso Esecutori


public class EsempioEsecutori {! public static void main(String[] args) {! ExecutorService esecutore = Executors.newFixedThreadPool(2);! esecutore.submit(new TaskCheImpiegaTreSecondi("TaskA"));! esecutore.submit(new TaskCheImpiegaTreSecondi("TaskB"));! esecutore.submit(new TaskCheImpiegaTreSecondi("TaskC"));! esecutore.shutdown();! }! }! ! class TaskCheImpiegaTreSecondi implements Runnable {! ! private final String nome;! !! public TaskCheImpiegaTreSecondi(String nome) {! this.nome = nome;! }! !! @Override! public void run() {! System.out.println(nome + " avviato alle: " + new Date());! try {! Thread.sleep(3000);! } catch (InterruptedException e) {}! System.out.println(nome + " completato alle: " + new Date());! }! }!

Esempio uso Esecutori (2)


TaskA avviato alle: Sun Mar 25 08:30:52 CEST 2012 TaskB avviato alle: Sun Mar 25 08:30:52 CEST 2012 TaskB completato alle: Sun Mar 25 08:30:55 CEST 2012 TaskA completato alle: Sun Mar 25 08:30:55 CEST 2012 TaskC avviato alle: Sun Mar 25 08:30:55 CEST 2012 TaskC completato alle: Sun Mar 25 08:30:58 CEST 2012!

Java ore degli ExecutorService predeniD allinterno della classe Executors.

Executors."
newSingleThreadExecutor() newFixedThreadPool(int n) newCachedThreadPool() Esegue al massimo un Task contemporaneamente Esegue al massimo n Task contemporaneamente Esegue un numero illimitato di Task contemporaneamente

Vantaggi uso Esecutori

Perme@ono di disaccoppiare il conce@o di Thread dal conce@o di Task. Uno stesso thread pu eseguire pi task in sequenza senza dover essere distru@o e ricreato aumenta le prestazioni Perme@ono di avere controllo sul numero di Thread in esecuzione dando la possibilit di accodare i Task in eccesso Esistono Esecutori pi avanzaD che perme@ono di schedulare lesecuzione di task ripetuD o dopo un intervallo di tempo specicato. Esempio: Interfaccia: ScheduledExecutorService! Implementazione: Executors.newScheduledThreadPool(int n)

Deadlock
Il deadlock una situazione di stallo in cui due (o pi) processi (o azioni) si bloccano a vicenda aspe@ando che uno esegua una certa azione (es. rilasciare il controllo su una risorsa come un le, una porta input/output ecc.) che serve allaltro e viceversa. Per evitare il deadlock necessario non avere dipendenze circolari per acquisire le risorse (lock) Il grafo delle risorse richieste non deve avere cicli! Esempio: Il thread1 ha il lock su A e vuole acquisire anche il lock su B Il thread2 ha il lock su B e vuole acquisire il lock su A Deadlock: entrambi i thread restano bloccaD nella speranza di acquisire il lock

Esempio Deadlock

class Oggetto A {! OggettoB b;! ...! synchronized void esegui() {! ...! b.test();! ...! }! }!

class Oggetto B {! OggettoA a;! ...! synchronized void test() {! ...! a.esegui();! ...! }! }!

OggeJ Thread-Safe
Quando due thread diversi accedono a un ogge@o contemporaneamente, possono vericarsi dei problemi come la comparsa di ConcurrentModica?onExcep?on o altri comportamenD anomali che non si vericano sempre. Questo Dpo di problemi dovuto al non-determinismo dellesecuzione parallela di pi thread. Il nome (gergale) con cui si chiamano queste anomalie heisenbug (da Heisemberg), poich tendono a non essere facilmente riproducibili in un ambiente controllato (es. debugging). Un ogge@o si denisce thread-safe quando perme@e (da contra@o) laccesso contemporaneo a pi thread senza anomalie (cio logge@o ore soltanto operazioni atomiche).

Collezioni e Mappe Thread-Safe


La maggior parte delle collezioni e mappe del package java.uDl NON sono thread-safe! Nel package java.uDl.concurrent possibile trovare delle versioni thread-safe di tu@e le collezioni e mappe che useremo. Ovviamente il costo di questa possibilit si paga in termini di prestazioni inferiori.

Collezioni e Mappe Thread-Safe


Non thread- safe Thread-safe (concurrent) Thread-safe (synchronized)

ArrayList LinkedList HashSet TreeSet HashMap TreeMap

CopyOnWriteArrayList - CopyOnWriteArraySet ConcurrentSkipListSet ConcurrentHashMap ConcurrentSkipListMap

CollecDons.synchronizedList(new ArrayList()) CollecDons.synchronizedList(new LinkedList()) CollecDons.synchronizedSet(new HashSet()) CollecDons.synchronizedSet(new TreeSet()) CollecDons.synchronizedMap(new HashMap()) CollecDons.synchronizedMap(new TreeMap())

Esercizio 1
Si scriva un programma che esegua 2 thread. Il primo thread fa le seguenD operazioni 10 volte:
Incrementa una variabile della classe che lo ha lanciato Stampa il valore di tale variabile

Il secondo thread fa le seguenD operazioni 10 volte:


Decrementa la variabile della classe che lo ha lanciato Stampa il valore di tale variabile

Al termine dei due thread viene stampato Fa@o!.

Esercizio 2
Un call center possiede 5 centralinisD. Nel momento di maggior auenza ci sono 100 clienD che chiamano. Una conversazione dura mediamente tra 4 e 6 secondi (per semplicit ...). Se tuJ i centralinisD sono occupaD il cliente resta in a@esa. Si simuli questa situazione con un programma Java in cui si ha un Thread per ogni cliente. Al termine di ogni chiamata il cliente stampa su video il tempo totale della sua chiamata (a@esa pi conversazione).

Esercizio 2 (estensioni)
Estensione 1: si modichi la soluzione dellesercizio 2 uDlizzando lEsecutore Executors.newCachedThreadPool Estensione 2: si modichi la soluzione dellesercizio 2 facendo in modo che i clienD messi in a@esa per primi siano i primi ad essere serviD.

Potrebbero piacerti anche