Java Concorrente
Java Concorrente
Ottobre 2005
Multithreading
Operazioni di I/O
Processori multipli
Svantaggi
Ogni thread usa ulteriori risorse di memoria
Overhead dovuto allo scheduling dei thread
Context switch:
Quando un thread viene sospeso e viene eseguito un altro
thread
Sono necessari diversi cicli di CPU per il context switch e se ci
sono molti thread questo tempo diventa significativo
Part I
Programmazione Multithreading in Java
Threads in Java
La classe principale `e java.lang.Thread
Passi principali:
1
Il metodo run()
Limplementazione di run() in Thread non fa niente
Il metodo run() costituisce lentry point del thread:
Ogni istruzione eseguita dal thread `e inclusa in questo metodo
o nei metodi invocati direttamente o indirettamente da run()
Un thread `e considerato alive finche il metodo run() non
ritorna
Quando run() ritorna il thread `e considerato dead
Primo Esempio
ThreadExample
public class ThreadExample extends Thread {
public void run() {
for (int i = 0; i < 20; ++i)
System.out.println("Nuovo thread");
}
}
Applicazione e thread
Esempio di sleep()
public class ThreadSleepExample extends Thread {
public void run() {
for (int i = 0; i < 20; ++i) {
System.out.println("Nuovo thread");
try { Thread.sleep(200); }
catch (InterruptedException e) { return; }
}
}
public static void main(String[] args) throws InterruptedException {
new ThreadSleepExample().start();
for (int i = 0; i < 20; ++i) {
System.out.println("Main thread");
Thread.sleep(200);
}
}
}
Thread corrente
Siccome una stessa sequenza di istruzioni pu`o essere eseguita
da thread differenti, potrebbe essere utile sapere quale thread
la sta effettivamente eseguendo
Si pu`o utilizzare il metodo statico currentThread() che
restituisce il thread (istanza di classe Thread) corrente
Thread & OOP
Lesempio seguente mostra anche che i metodi di unistanza
Thread possono essere eseguiti anche da unaltro thread, non
necessariamente dal thread dellistanza.
Thread & istanze
` sbagliato assumere che allinterno di un metodo di una classe
E
thread il this corrisponda al thread corrente!
Esempio: currentThread()
public class CurrentThreadExample extends Thread {
private Thread creatorThread;
public CurrentThreadExample() {
creatorThread = Thread.currentThread();
}
public void run() {
for (int i = 0; i < 1000; ++i)
printMsg();
}
public void printMsg() {
Thread t = Thread.currentThread();
if (t == creatorThread) {
System.out.println("Creator thread");
} else if (t == this) {
System.out.println("New thread");
} else {
System.out.println("Unknown thread");
}
}
}
Thread e nomi
Altro esempio
class EsempioThread1 extends Thread {
private char c;
public EsempioThread1( String name, char c )
{
super( name );
this.c = c;
}
public void run()
{
for (int i = 0; i < 100; ++i)
System.out.print(c);
System.err.println( "\n" + getName() + " finito" );
}
}
Altro esempio
public class ThreadTest1 {
public static void main( String args[] ) {
EsempioThread1 thread1, thread2, thread3, thread4;
thread1 =
new EsempioThread1( "thread1", @ );
thread2 =
new EsempioThread1( "thread2", * );
thread3 =
new EsempioThread1( "thread3", # );
thread4 =
new EsempioThread1( "thread4", + );
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
Figure: Thread
hierarchy
Esempio di Runnable
class EsempioThread2 implements Runnable {
String name;
private int sleepTime;
public EsempioThread2( String name ) {
this.name = name;
// pausa fra 0 e 5 secondi
sleepTime = (int) ( Math.random() * 5000 );
System.out.println( "Name: " + name + "; sleep: " + sleepTime );
}
public void run() {
for (int i = 0; i < 5; ++i) {
System.out.println (name + " : in esecuzione.");
try { Thread.sleep(sleepTime); }
catch (InterruptedException e) {}
}
System.err.println( name + " finito" );
}
}
Esempio di Runnable
public class ThreadTest2 {
public static void main( String args[] )
{
Thread thread1, thread2, thread3, thread4;
thread1
thread2
thread3
thread4
=
=
=
=
new
new
new
new
Thread(new
Thread(new
Thread(new
Thread(new
EsempioThread2(
EsempioThread2(
EsempioThread2(
EsempioThread2(
"thread1"
"thread2"
"thread3"
"thread4"
));
));
));
));
Metodo isAlive()
Pu`
o essere utilizzato per testare se un thread `e vivo:
quando viene chiamato start() il thread `e considerato alive
il thread `e considerato alive finche il metodo run() non
ritorna
Esempio: join()
public class ThreadTestExitJoin {
public static void main(String args[]) throws Exception {
Thread thread1, thread2, thread3, thread4;
thread1 = new EsempioThreadSleep("thread1");
thread2 = new EsempioThreadSleep("thread2");
thread3 = new EsempioThreadSleep("thread3");
thread4 = new EsempioThreadSleep("thread4");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread1.join();
thread2.join();
thread3.join();
thread4.join();
System.exit(0);
}
}
ATTENZIONE
Assolutamente da EVITARE!
Implementazione corretta
class EsempioThreadSleep extends Thread {
public void run() {
for (int i = 0; i < 5; ++i) {
System.out.println(name + " : in esecuzione.");
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
System.err.println(name + " interrotto");
break;
}
}
System.err.println(name + " finito");
}
}
Interrompere i thread
public class ThreadTestInterrupt {
public static void main(String args[]) throws Exception {
// crea i thread e li lancia
Thread.sleep(2000);
thread1.interrupt();
thread2.interrupt();
thread3.interrupt();
thread4.interrupt();
thread1.join();
thread2.join();
thread3.join();
thread4.join();
System.exit(0);
}
}
Implementazione scorretta
class EsempioThreadMalicious extends Thread {
public void run() {
for (int i = 0; i < 20; ++i) {
System.out.println(name + " : in esecuzione.");
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
System.err.println(name + " interrotto ma continuo :->");
}
}
System.err.println(name + " finito");
}
}
Utilizzo di timeout
public class ThreadTestInterruptMaliciousTimeout {
public static void main(String args[]) throws Exception {
Thread thread1, thread2, thread3, thread4;
thread1 = new EsempioThreadSleep("thread1");
thread2 = new EsempioThreadSleep("thread2");
thread3 = new EsempioThreadSleep("thread3");
thread4 = new EsempioThreadMalicious("thread4");
// ... fa partire i thread...
thread1.interrupt();
thread2.interrupt();
thread3.interrupt();
thread4.interrupt();
thread1.join(1000);
thread2.join(1000);
thread3.join(1000);
thread4.join(1000);
System.exit(0);
}
}
Ulteriori problemi
Esempio: isInterrupted
Collaborazione: Thread.yield()
Esercizio
Esercizio 2
Variazione: la possibilit`a di identificare allinterno del contenitore i
thread con i loro nomi:
insert: come sopra ma non inserisce il thread se ne esiste gi`a
uno con lo stesso nome (e ritorna false)
get(name): ritorna il thread col nome specificato (o null
altrimenti)
interrupt(name): interrompe il thread col nome specificato
join(name): attende la terminazione del thread specificato
remove(name): rimuove dal contenitore il thread selezionato,
lo interrompe e ne attende la terminazione
Part II
Accesso Concorrente a Risorse Condivise
Condivisione dati
Esempio (errato)
class AssegnatoreErrato {
private int tot posti = 20;
public boolean assegna posti(String cliente, int num posti) {
System.out.println("--Richiesta di " + num posti + " da " + cliente);
if (tot posti >= num posti) {
System.out.println("---Assegna " + num posti + " a " + cliente);
tot posti -= num posti;
return true;
}
return false;
}
int totale posti() { return tot posti; }
}
Problemi
Se pi`
u thread eseguono quella parte di codice in parallelo e si
ha un context switch nel mezzo della transazione, la risorsa
sar`a in uno stato inconsistente:
Il numero di posti assegnato alla fine sar`a maggiore di quello
realmente disponibile!
Race condition
Codice non rientrante
Esempio di client
public class Richiedente extends Thread {
private int num posti;
private Assegnatore assegnatore;
public Richiedente(String nome, int num posti, Assegnatore assegnatore) {
super(nome);
this.num posti = num posti;
this.assegnatore = assegnatore;
}
public void run() {
System.out.println("-" + getName() + ": richiede " + num posti + "...");
if (assegnatore.assegna posti(getName(), num posti))
System.out.println("-" + getName() + ": ottenuti " + num posti
+ "...");
else
System.out.println("-" + getName() + ": posti non disponibili");
}
}
Esempio (errato)
public class AssegnaPostiErrato {
public static void main(String args[]) throws InterruptedException {
AssegnatoreErrato assegnatore = new AssegnatoreErrato ();
Richiedente client1 =
new Richiedente("cliente1",
Richiedente client2 =
new Richiedente("cliente2",
Richiedente client3 =
new Richiedente("cliente3",
Richiedente client4 =
new Richiedente("cliente4",
3, assegnatore);
10, assegnatore);
5, assegnatore);
3, assegnatore);
Accesso sincronizzato
Accesso sincronizzato
Accesso sincronizzato
Quando un metodo `e synchronized lo si pu`o invocare su un
oggetto solo se si `e acquisito il lock su tale oggetto
Quindi i metodi synchronized hanno accesso esclusivo ai
dati incapsulati nelloggetto (se a tali dati si accede solo con
metodi synchronized)
I metodi non synchronized non richiedono laccesso al lock
e quindi si possono richiamare in qualsiasi momento
Variabili locali
Poiche ogni thread ha il proprio stack, se pi`
u thread stanno
eseguendo lo stesso metodo, ognuno avr`a la propria copia delle
variabili locali, senza pericolo di interferenza.
Monitor
Monitor in Java
Oggetto con metodi synchronized (Ogni oggetto pu`o essere
un monitor)
Solo un thread alla volta pu`o eseguire un metodo
synchronized su uno stesso oggetto:
Prima di eseguirlo deve ottenere il lock (mutua esclusione)
Appena uscito dal metodo il lock viene rilasciato
Esempio (corretto)
class Assegnatore {
private int tot posti = 20;
public synchronized boolean assegna posti(String cliente, int num posti) {
System.out.println("--Richiesta di " + num posti + " da " + cliente);
if (tot posti >= num posti) {
System.out.println("---Assegna " + num posti + " a " + cliente);
tot posti -= num posti;
return true;
}
return false;
}
int totale posti() { return tot posti; }
}
In dettaglio: synchronized
Quando un thread deve eseguire un metodo synchronized su
un oggetto si blocca finche non riesce ad ottenere il lock
sulloggetto
Quando lo ottiene pu`o eseguire il metodo (e tutti gli altri
metodi synchronized)
Gli altri thread rimarranno bloccati finche il lock non viene
rilasciato
Quando il thread esce dal metodo synchronized rilascia
automaticamente il lock
A quel punto gli altri thread proveranno ad acquisire il lock
Solo uno ci riuscir`a e gli altri torneranno in attesa
Blocchi synchronized
Singoli blocchi di codice possono essere dichiarati
synchronized su un certo oggetto
Un solo thread alla volta pu`o eseguire un tale blocco su uno
stesso oggetto
Permette di minimizzare le parti di codice da serializzare ed
aumentare il parallelismo
Equivalenza
public synchronized int read() {
return theData;
}
Blocchi synchronized
Esempio (alternativa)
public class Assegnatore2 extends Assegnatore {
private int tot posti = 20;
public boolean assegna posti(String cliente, int num posti) {
System.out.println("--Richiesta di " + num posti + " da " + cliente);
synchronized (this) {
if (tot posti >= num posti) {
System.out.println("---Assegna " + num posti + " a " + cliente);
tot posti -= num posti;
return true;
}
}
return false;
}
int totale posti() { return tot posti; }
}
Blocchi synchronized
Variabili statiche
metodi e blocchi synchronized non assicurano laccesso
mutuamente esclusivo ai dati statici
I dati statici sono condivisi da tutti gli oggetti della stessa
classe
Attenzione
Il lock a livello classe NON si ottiene quando ci si sincronizza su un
oggetto di tale classe e viceversa.
Campi volatili
Un campo pu`o essere dichiarato come volatile
Java richiede che un campo volatile non deve essere
mantenuto in una memoria locale
tutte le letture e scritture devono essere fatte in memoria
principale (condivisa)
Le operazioni su campi volatili devono essere eseguite
esattamente nellordine richiesto dal thread
le variabili long e double devono essere lette e scritte in
modo atomico
` comodo quando un solo thread modifica il valore di un campo e
E
tutti gli altri thread lo possono leggere in ogni momento.
Esempio: ProduttoreConsumatore
Esempio: ProduttoreConsumatore
Eccezioni
Wait:
IllegalMonitorStateException - if the current thread is
not the owner of the objects monitor.
InterruptedException - if another thread has interrupted
the current thread.
Notify:
IllegalMonitorStateException - if the current thread is
not the owner of the objects monitor.
Segnature
public final native void notify()
throws IllegalMonitorStateException // RuntimeException
public final native void notifyAll()
throws IllegalMonitorStateException // RuntimeException
public final native void wait()
throws InterruptedException,
IllegalMonitorStateException // RuntimeException
public final native void wait(long msTimeout)
throws InterruptedException,
IllegalMonitorStateException, // RuntimeException
IllegalArgumentException // RuntimeException
public final native void wait(long msTimeout, int nanoSec)
throws InterruptedException,
IllegalMonitorStateException, // RuntimeException
IllegalArgumentException // RuntimeException
Variabili Condizione
Il meccanismo wait/notify non richiede che una variabile sia
controllata da un thread e modificata da un altro
Comunque, `e bene utilizzare sempre questo meccanismo
insieme ad una variabile
In questo modo si eviteranno le missed notification e le early
notification
La JVM (in alcune implementazioni) pu`o risvegliare thread in
attesa indipendentemente dallapplicazione (spurious
wake-up).
Java
non mette a disposizione delle vere e proprie variabili condizione:
devono essere implementate dal programmatore.
Scenario di problema
Ci sono pi`
u thread in attesa sullo stesso oggetto ma su
condizioni differenti
si usa notify ed in questo modo se ne risveglia solo uno
il thread testa la sua condizione che per`o risulta ancora falsa e
ritorna in attesa
gli altri thread non hanno la possibilit`a di testare la loro
condizione
DEADLOCK!
Gestire InterruptedException
public synchronized void startWrite()
throws InterruptedException
{
while (readers > 0 || writing) {
waitingWriters++;
wait();
waitingWriters--;
}
writing = true;
}
Gestire InterruptedException
Versione corretta:
public synchronized void startWrite()
throws InterruptedException
{
try {
while (readers > 0 || writing) {
waitingWriters++;
wait();
waitingWriters--;
}
writing = true;
} catch (InterruptedException e) {
waitingWriters--;
throw e;
}
}
Gestire InterruptedException
Versione corretta (alternativa):
public synchronized void startWrite()
throws InterruptedException
{
while (readers > 0 || writing) {
waitingWriters++;
try {
wait();
} finally {
waitingWriters--;
}
}
writing = true;
}