Programmazione concorrente in Java (vecchio modello)
-
Upload
davide-carboni -
Category
Technology
-
view
7.995 -
download
2
description
Transcript of Programmazione concorrente in Java (vecchio modello)
Concurrent Programming in Java
Guida alle API e descrizione del modello di concorrenza in Java
Autore: Davide Carboni <[email protected]>
© Copyright 2000-2001 CRS4 (www.crs4.it)
Concorrenza
Fare più di una cosa per volta
Esempio:
un web browser fa il download di una pagina
HTML, suona un’ audioclip, attende un input da
tastiera, visualizza nella barra di stato il bitrate ect..
Ma è vera concorrenza ?
� In alcuni sistemi differenti task possono essere assegnati a differenti CPU
� In altri una singola CPU divide il proprio tempo fra i vari task , la sensazione è quella di esecuzione simultanea.
Quali Applicazioni
� Number Crunching
� I/O processing
� Simulation
� GUI
� Embedded Systems
Clusters
Ogni macchina esegue una unità di esecuzione concorrente
Facile da amministrare ma svantaggioso per la difficoltà di
condividere risorse e passare messaggi
Multi Processing
� Il processo è un’astrazione che fornisce il SO
� Ogni processo tipicamente rappresenta un programma durante l’esecuzione
� Ogni processo gestisce il suo spazio dati che non condivide
� La comunicazione tra processi avviene tramite IPC, pipes ...
Multi Threading
� Un processo può avere uno o più thread, ognuno dei quali è ancora una unità di esecuzione indipendente.
� I thread appartenenti allo stesso processo condividono la memoria, i file ed altre risorse
� le loro azioni sono sincronizzate grazie ai lock e al meccanismo wait,notify
Multi Threading
� I thread possono essere schedulati dalkernel nei sistemi in cui il kernel li supporta (linux, windows, …)
Multi Threading
� Un processo può gestire il suomultithreading internamente anche se ilkernel non supporta i thread.
� Questo viene fatto da processi come la JVM, SQL server etc…
� La JVM fornisce un costrutto concorrente anche se il SO è “monotaskoso”
I Thread in Java
� Java fornisce diversi strumenti per la programmazione concorrente:
java.lang.Thread class
java.lang.Object class
java.lang.Runnable interface
synchronized keyword
I Thread in Java (FAQ)
� Come parte un Thread ?
� Quando un Thread è in esecuzione quale codice esegue ?
� In quali/quanti stati può essere un Thread ?
� Come e perché i Thread cambiano il loro stato ?
Come parte un Thread ?
� Un Thread è un’istanza della classe java.lang.Thread o di una sua sottoclasse ma inizia la sua esecuzione SOLO quando viene invocato il suo metodo start()
esempio:
Thread t=new MyThread(Qualcosa);
t.start();
Cosa fa il metodo start()
� Il metodo start() registra l’istanza del Thread in un “pezzo di software” chiamato Thread Scheduler
� Il Thread Scheduler può essere interno alla JVM oppure esterno (servizio del SO)
� Il Thread Scheduler decide quale Thread è in esecuzione su una data CPU e in dato momento.
Priorità
Ogni thread ha la sua priorità che va da
1 (bassa) a 10 (alta)
t.getPriority() restituisce l’attuale priorità del Thread t
t.setPriority(8) impone a 8 la priorità del Thread t
Ancora l’esempio
� esempio:Thread t=new MyThread(Qualcosa);
t.start();
� a questo punto ci sono almeno 2 Thread, uno è il main che ha eseguito le due linee sopra e l’altro è t
Quando un Thread è in
esecuzione quale codice esegue ?
� La risposta è:
Il codice contenuto nel suo metodo run ( )
� Il metodo run( ) della classe java.lang.Thread è vuoto quindi i nostri Thread faranno l’overriding
Esempio:overriding di run( )
public class CounterThread extends Thread
{
public void run()
{
for(int i=1;i<=10;i++)
System.out.println(“conto “+i);
}
}
Esempio:lancio il Thread
CounterThread c=new CounterThread;
c.start( ); // non uso c.run( )
Se usassi c.run( ) otterrei l’esecuzione immediata di run( ) senza inserire c
nello scheduler
Perché questo approccio non è
soddisfacente ?
� A causa dell’ereditarietà singola la nostra classe thread non può estendere altre classi.
� Questo è un grosso problema nel design di architetture complesse.
java.lang.Runnable
� La classe java.lang.Thread ha un
secondo costruttore :
public Thread(Runnable eseguibile)
� L’interfaccia Runnable ha il solo metodo
public void run();
java.lang.Runnable
� Grazie a Runnable posso costruire
architetture più complessepublic class Derived extends Base
implements Runnable{
public void run( ) {
faiqualcosainmultithreading;}
public int f(int x){ ...}
public Object g(byte [] x){ ...}
}
java.lang.Runnable
� Derived d=new Derived( … );
� Thread t=new Thread(d);
� t.start( );
� d.f(x); //non c’entra con i thread
La morte dei Thread
� I thread “muoiono” quando il metodo run ( ) ritorna
� In realtà sono dei non-morti perché pur non essendo più eseguibili sono ancora degli oggetti Java validi e tutti i loro metodi possono essere ancora invocati
Gerarchia di classi
Class Object
Class Base
Class Derived
Class Thread
Runnable
La morte dei Thread
� Valgono perciò 2 regole:
1. Un thread il cui metodo run( ) ritorna NON può essere più “restartato”
2. Se l’istanza del thread non viene Gcollected i suoi metodi possono essere invocati
La morte dei Thread
� Non è una buona strategia quella di creare, usare e buttare via dei thread perché la costruzione e lo scheduling di un thread è un’operazione onerosa.
� Per ovviare a questo esistono tecniche di Thread Pooling, ossia mi tengo un’insieme di Thread pronti all’uso
Esempio: un web server in java
public class Server
{
int port=9000;
Socket socket=null;
ServerSocket sock_serv=null;
public void boot(int port) { …}
…
[snip]
}
Esempio: un web server in java
public void boot(int port) throws Exception
{
this.port=port;
sock_serv=new ServerSocket(port);
MyThread aThread=null;
while(true)
{
socket=sock_serv.accept();
aThread=new MyThread(socket);
aThread.start();
}
}
Esempio: un web server in java
public class MyThread extends Thread
{
Socket socket=null;
MyThread(Socket s)
{
socket=s;
}
public void run()
{
…[implementation]…
}
}
Esempio: un web server in java
public void run(){
try{
PrintWriter writer=new
PrintWriter(socket.getOutputStream(),true);
BufferedReader reader=
new BufferedReader(
new
InputStreamReader(socket.getInputStream()));
…
[snip]
…
Esempio: un web server in java
public void run(){
…
[snip]
…
String input=reader.readLine();
if(input.startsWith("GET"))
{
writer.println("<html><body><p><h1>");
writer.println("CIAO A TUTTI");
writer.println("</h1></p></body></html>");
}
socket.close();
return;
}//end of try block
catch(Exception e){ … }
}
In quali/quanti stati può essere un
Thread ?
� Running
� Ready-to-run
� Suspended, Blocked, Sleeping (not ready)
� Monitor states (sync,wait/notify sequence)
� Dead
In quali/quanti stati può essere un
Thread ?
running
Ready-to-run
suspended sleeping blockedMonitor
states
Transizioni di stato
running
Ready-to-run
� t.yield( ) causa al
Thread t il passaggio da running a ready-to-run
� in questo modo un Thread diverso da t ha la possibilità immediata di passare allo stato running
Transizioni di stato
suspend( ) e resume( ) sono
deprecati in java1.2
running
Ready-to-run
suspended
suspend()
resume()
Transizioni di stato
� Thread.sleep(long)
causa al thread che
lo invoca il
passaggio allo stato
sleeping
� E’ un sistema di
timing ma non è
precisissimo
running
Ready-to-run
sleeping
sleep(long)
Time expires
or interrupted
Transizioni di stato
� Alcuni metodi
causano il blocco
del thread
invocante, questo
capita quando di
mezzo c’è un device
che non risponde
subito come un’
interfaccia di rete
running
Ready-to-run
blocked
Blocking
method
Interruption
or change in
blocking
device
Blocco di un Thread
aSocket=new Socket(“www.unica.it”,80);
InputStream i=aSocket.getInputStream( );
int b = i.read();
// qui ci possiamo bloccare
Se la “rete” non è pronta a fornirci il int richiesto il thread in esecuzione si blocca !!
Perché bisogna sincronizzare
Cattivo Esempio
class Pari{
private int n=0;
public int next(){
n++; n++;
return n;
}
}
Perché bisogna sincronizzare
� Thread A
– read 0
– write 1
– read 2
– write 3
– return 3
� Thread B
– read 1
– write 2
– read 2
– write 3
– return 3
Codice sincronizzato
� Ogni istanza di java.lang.Object o di
una sua sottoclasse possiede un LOCK
� Un valore primitivo (int,long,…) non possiede
nessun LOCK
� Un array di tipi primitivi o Object possiede un
LOCK
synchronized
synchronized(a)
{
istruz_1;
istruz_2;
…
istruz_k;
}
� Solo il thread che
acquisisce il LOCK
di a può eseguire il
blocco.
� Gli altri thread in
competizione per il
LOCK si bloccano in
uno stato detto di
seeking
synchronized
L’approccio precedente consente di sincronizzare un blocco di codice di un oggetto O con un LOCK di un altro oggetto a
Potente possibilità ma pericoloso !
Lo stato seeking lock
running
scheduled
Enter
synchronized
code
Lock obtainedready to run
seeking
synchronized
Object f( )
{
synchronized(this){ ...codice... }
}
EQUIVALE
synchronized Object f( )
{
...codice...
}
synchronized
Class S{
public synchronized void f( ) { … }
public synchronized void g( ) { … }
public void h( ) { … }
}
invocare f() e g() su istanze di S ci costringe a competere per il lock, però possiamo invocare liberamente h( )
synchronized
O = new S( );
O.f( ); //aspetto che il LOCK sia mio
… ; //al ritorno di f( ) ho rilasciato
il LOCK
O.h( ); // la eseguo e basta
O.g( ); // aspetto che il LOCK sia mio
… ; //ho già rilasciato il LOCK
Atomicità
Un oggetto si dice atomico se:
� tutti i metodi sono sincronizzati
� non ci sono attributi pubblici o altre violazioni dell’incapsulamento
� tutti i metodi sono finiti (senza loop infiniti)
� lo stato dell’oggetto è costruito in modo consistente
� lo stato è consistente sia alla fine che all’inizio di ogni metodo anche in presenza di eccezioni
Thread Safe
Le classi di oggetti che implementano l’atomicità sono thread safe, ossia il
loro stato non può essere corrotto dall’esecuzione di più thread
simultaneamente
Deadlock
� Anche se un oggetto atomico è thread safe i
thread che lo usano possono finire in uno
stato detto Deadlock (abbraccio mortale)
lock
lock
O1 O2
Monitor
� Un monitor è un oggetto che può bloccare e risvegliare thread
� In Java ogni oggetto dotato di codice synchronized è un monitor
Monitor: ma a che servono?
� Attraverso le primitive wait e notify possiamo coordinare le attivita’ di 2 o piu’ thread
Esempio di wait, notify
public class SpaceProvider implements
net.jini.discovery.DiscoveryListener
{
[snip]
…
private JavaSpace space;
…
[snip]
…
public synchronized JavaSpace getSpace(String category) throws
Exception { … }
[snip]
…
public synchronized void discovered(DiscoveryEvent e) { …. }
}
Esempio di wait, notify
public synchronized JavaSpace getSpace(String
category) ... {
if(space==null){
LookupDiscovery ld=new LookupDiscovery(…);
ld.addDiscoveryListener(this);
wait(60000);
}
if(space == null) {
throw new Exception ("No space found”);
}
return space;
}
Questo thread si blocca
in monitor-state
Esempio di wait, notify
public synchronized void discovered(DiscoveryEvent e) {
ServiceRegistrar reggie=e.getRegistrars()[0];
…
[snip]
…
try{
space=(JavaSpace)reggie.lookup(…);
if(space!=null)
{
notify();
}
}
catch(Exception ex){
ex.printStackTrace();
}
}
A questo punto l’altro thread passa
in ready-to run
Esempio di wait, notify
public synchronized JavaSpace getSpace(String
category) ... {
if(space==null){
LookupDiscovery ld=new LookupDiscovery(…);
ld.addDiscoveryListener(this);
wait(60000);
}
if(space == null) {
throw new Exception ("No space found”);
}
return space;
}
Esempio di wait, notify
Ricapitolando
Un monitor e’ un qualsiasi oggetto
che possiede del codice synchronized
Esempio di wait, notify
Ricapitolando
� wait causa al thread che la
esegue la cessione del lock ed il
blocco in monitor-state.
�Se il thread che esegue wait non
detiene il lock viene lanciata un’eccezione
Esempio di wait, notify
Ricapitolando
�notify causa al thread che la
esegue la cessione del lock
�notify risveglia un thread che ha
eseguito wait sul monitor
� Il thread risvegliato passa dallo stato monitor-state allo stato ready-to-run
Altri modi per coordinare I thread
� wait e notify coordinano le attivita’
dei thread usando un lock come elemento di coordinazione
� join coordina un thread
sull’esecuzione di un’altro thread
Altri modi per coordinare i thread
public static void main(String[] args) �…{
System.out.println("main thread:messaggio 1");
Runnable r1= new Runnable(){
public void run(){
for (int i= 0; i < 10; i++) {
System.out.println("r1 thread:messaggio "+ i);
try{Thread.sleep(1000);}
catch (Exception e)
{e.printStackTrace();}
}
}
};
…[snip]
Altri modi per coordinare I thread
…[snip]
Thread t1= new Thread(r1);
t1.start();
System.out.println("main thread:messaggio 2");
t1.join();
System.out.println("main thread:messaggio 3");
}
Altri modi per coordinare I threadOutput:
main thread:messaggio 1
r1 thread:messaggio 0
main thread:messaggio 2
r1 thread:messaggio 1
r1 thread:messaggio 2
r1 thread:messaggio 3
r1 thread:messaggio 4
r1 thread:messaggio 5
r1 thread:messaggio 6
r1 thread:messaggio 7
r1 thread:messaggio 8
r1 thread:messaggio 9
main thread:messaggio 3
Altri modi per coordinare I thread
t
Main thread messaggio 1
Main thread messaggio 2
r1 thread messaggio 0
r1 thread messaggio 1
r1 thread messaggio 9
….
Main thread messaggio 3
main()
t1.start()
t1.join()
Limiti del modello
� Operazioni deprecate stop(), suspend(), resume()
� Mancanza del back off da seeking-lock
� Denial-of-service in caso di synchronized(obj)
� Block structured Locking
Operazioni deprecate
stop()
Questa operazione e’ thread-unsafe perche' costringe il thread a terminare rilasciando i lock degli oggetti.
Gli oggetti rilasciati possono finire in uno stato inconsistente.
Operazioni deprecate
suspend(),resume()
Sospendere un thread e’ pericoloso perche’ il thread sospeso mantiene i lock degli oggetti senza procedere.
Operazioni deprecate
suspend(),resume()
Esempio: 2 Thread T1 e T2
T1 possiede il lock O
T2 sospende T1
T2 cerca di eseguire un blocco sincronizzato su O
T2 e’ bloccato in seeking-lock su O
T1 non rilascera’ mai O perche’ e’ sospeso
T1 e T2 sono entrambi bloccati, in assenza di un terzo thread il sistema resta bloccato.
Mancanza del back off da
seeking-lock
� Un thread T che cerca di eseguire codice sincronizzato su un lock O viene messo in competizione per l’acquisizione di O e NON puo’ tirarsi indietro
Denial-of-service
� Qualunque metodo puo sincronizzare parte del suo codice su un qualunque oggetto O di cui abbia visibilita’ tramite synchronized(O)
� Un meccanismo di accesso controllato ai lock potrebbe ridurre la possibilita; di denial-of-service
Block structured Locking
� Lo scope di acquisizione rilascio e’ strettamente a livello di blocco o di metodo, non e’ possibile acquisire il lock in un metodo M1 e rilasciarlo in un metodo M2.
Bibliografia
� Thinking in Java, Bruce Eckel, Prentice Hall
� Concurrent Programming in Java, Doug Lea,
Addison Wesley
� The complete Java 2 certification guide,
S.Roberts et al., Sybex
� The Java Language Specifications, J. Gosling
et al., Addison Wesley
Q & A