27/03/2015 1 Concorrenza multithreading in Java RICHIAMI SU

27/03/2015
Concorrenza
multithreading in Java
RICHIAMI SU PARALLELISMO
1
Parallelismo = multitasking
Possiamo scrivere un programma in cui
diverse attività (task) evolvono in parallelo da
un punto di vista logico
Queste possono evolvere in parallelismo
fisico
Sistemi multi-processore
In passato erano costosi e riservati ad
applicazioni speciali
Oggi sistemi multiprocessore sono
largamente disponibili a livello di massa
Massimo parallelismo fisico
ogni attività parallela ha a disposizione un processore fisico
altrimenti esse vengono eseguite da
processori condivisi secondo modalità decise
da uno scheduler, in generale non
controllabile dal programmatore
3
4
1
27/03/2015
Caso 1: Task singolo
Multitasking su singolo
processore
Approccio
Processore esegue un task
Passa velocemente a un altro
Sotto il governo dello scheduler
Il processore sembra lavorare sui diversi
task concorrentemente
Passaggio da un task all'altro nei
momenti di inattività o per esaurimento
di budget temporale ( time sharing )
5
Multitasking a livello di processi
Processo
Programma eseguibile caricato in memoria
Ha un suo spazio di indirizzi (variabili e
strutture dati in memoria)
Ogni processo esegue un diverso
programma
I processi comunicano via OS, files, rete
Può contenere più thread
7
Caso 2: Due task
6
Multitasking a livello di thread
Thread è un'attività logica sequenziale
Un thread condivide lo spazio di indirizzi
con gli altri thread del processo e
comunica via variabili condivise
Ha un suo contesto di esecuzione
(program counter, variabili locali)
Si parla spesso di light-weight
process
8
2
27/03/2015
Motivazioni ed esempio
La suddivisione in processi e task coglie la
struttura logica del problema
componenti (processi) che interagiscono
ogni componente composto da diversi thread
LA SOLUZIONE DI JAVA:
THREADS
Multiple richieste simultanee
da web browser
da multipli server, ciascuno
multi-threaded
9
Costrutti per la concorrenza a
livello di linguaggio
C e C++ sono linguaggi «single-threaded»
Multitasking in C e C++ richiede di appoggiarsi al
sistema operativo, con opportune call alle primitive
multithread del SO
Java contiene primitive multitasking al suo
interno (es. Classe Thread).
Es.di thread: il Java garbage collector.
Ciò consente di scrivere sw multithread senza
occuparsi del SO
Alcuni dettagli del multithread in Java sono pero' SOdependent
Questo ha consentito sviluppo di librerie di alto livello
che semplificano la costruzione di sw multitasking
Thread in Java
metodo 1
1. Definire una classe che eredita da Thread e
contiene il metodo run()
class ListSorter extends Thread {
public void run() {
gli statements del thread che si vuol definire
}
}
2. Creare istanza della classe
ListSorter concSorter = new ListSorter(list1)
3. Invocare il metodo start(), che a sua volta
chiama run()
lancia il thread e
concSorter.start();
ritorna
immediatamente12
3
3
27/03/2015
Esempio da Java Tutorial
Esempio
public class HelloThread extends Thread {
@Override
public void run() {
System.out.println("Hello from a thread!");
}
public static void main(String args[]) {
(new HelloThread()).start();
}
}
13
14
Differenti interleaving
Nota
La classe Thread in realtà implementa
un'interfaccia chiamata Runnable
L'interfaccia Runnable definisce un solo
metodo run che contiene il codice del
thread
Su ciò si basa un modo alternativo
15
16
4
27/03/2015
Thread in Java
metodo 2
1. Definire una classe che: 1) implementa l'interfaccia
Runnable, 2) eventualmente estende altra classe, 3)
contiene il metodo run()
metodo più
class ListSorter implements Runnable {
generale
si usa se si deve
public void run() {
gli statements del thread che si vuol definireereditare da
}
qualche
}
classe
2. Creare istanza
Esempio
public class HelloRunnable implements Runnable {
public void run() {
System.out.println("Hello from a thread!");
}
ListSorter concSorter = new ListSorter(list1);
public static void main(String args[]) {
(new Thread(new HelloRunnable())).start();
}
3. Creare thread
Thread myThread = new Thread(concSorter);
4. Attivare metodo run, attraverso metodo start()
myThread.start()
17
Ciclo di vita di un thread
(semplificato)
born
La classe Thread prevede anche metodi e
caratteristiche che consentono un controllo molto (a
volte troppo!) fine della concorrenza, spesso
dipendente dal SO e che qui IGNORIAMO, p.es.:
start
ready
scheduler
lock
18
Dettagli vari
costruito
running I/O
}
sleep(x) sospende il thread per x msec (stato sleeping)
interrupt(): inviare interrupt al thread (ad. es. per terminarlo); il
suo run() di norma lancia InterruptedException;
fine I/O
fine lock
Ogni thread ha priorità (compresa fra
Thread.Min_Priority e Thread.Max_Priority, di default
Thread.Norm_Priority)
blocked
dead
19
La priorità è usata dallo scheduler per decidere quali thread
sono running
5
27/03/2015
Dati condivisi
Esempio di run
Può essere necessario imporre che certe
sequenze di operazioni che accedono a
dati condivisi vengano eseguite dai task
in mutua esclusione
class ContoCorrente {
private float saldo;
public ContoCorrente (float saldoIniz)
{ saldo = saldoIniz; }
public void deposito (float soldi)
{ saldo += soldi; }
public void prelievo (float soldi)
{ saldo -= soldi; }
che succede se
due
task concorrenti
cercano l'uno di
depositare e l'altro
di prelevare dallo
stesso conto?
1. All'inizio saldo = 100
2. Un Thread1 chiama deposito(50), inizia esecuzione,
calcola il valore di saldo+soldi=150 ma non fa in
tempo a scriverlo in saldo
3.
prelievo ed
esegue completamente saldo -= soldi: ora saldo=50.
4. Thread1 continua e esegue saldo=150.
5. Abbiamo prelevato 50 e depositato 50 ma il nostro
saldo è aumentato!
public void deposito (float soldi){ saldo += soldi; }
public void prelievo (float soldi) { saldo -= soldi; }
21
Es: Interferenza
Soluzione: Sequenze atomiche
Esecuzione concorrente di x+=y e x-=y.
Ciascun thread esegue tre operazioni:
1.Lettura del valore di x e y
2.Calcolo di espressione (x+y oppure x-y)
3.Scrittura in variabile x
L'esecuzione concorrente di x+=y e x-=y
può avere uno dei seguenti effetti:
- incrementare x di y
- lasciare x immutata
- decrementare x di y
22
23
Generalizzazione del problema
a volte si vuole che certe sequenze di
istruzioni/interi metodi vengano eseguite in
isolamento, senza interleaving con
istruzioni di altre sequenze parallele che
altri thread potrebbero eseguire
si parla di sequenze atomiche
In java quasi nessuna istruzione è atomica
(p.es. anche x++ è in realtà una sequenza
di istruzioni)
24
6
27/03/2015
Imporre ordinamenti
Come rendere i metodi "atomici"
A volte, oltre a voler l'atomicità di certe
sequenze, si vogliono imporre certi
ordinamenti nell'esecuzione di operazioni
Per esempio, l'operazione A eseguita da un
thread deve essere eseguita prima (happens
before) dell'operazione B di un altro thread
Di solito ciò deriva dal fatto di voler garantire
certe proprietà di consistenza della memoria
(stato della computazione)
La parola chiave "synchronized"
class ContoCorrente {
private float saldo;
public ContoCorrente (float saldoIniz)
{ saldo = saldoIniz; }
synchronized public void deposito (float soldi)
{ saldo += soldi; }
synchronized public void prelievo (float soldi)
{ saldo -= soldi; }
25
Esempio: contatore sincronizzato
public class SynchronizedCounter {
private int c = 0;
26
Metodi synchronized
Java associa un intrinsic lock (monitor) a
ciascun oggetto che ha almeno un metodo
synchronized
public synchronized void increment() {
c++;
}
Ogni estensione di object può avere metodi synchronized
Quando un metodo synchronized è invocato:
public synchronized void decrement() {
c--;
}
public synchronized int value() {
return c;
}
}
se l'oggetto non è bloccato (ossia nessun metodo
synchronized è in esecuzione sull'oggetto), l'oggetto
viene bloccato (locked) e quindi il metodo è eseguito
se l'oggetto è bloccato, il task chiamante viene
sospeso fino a quando il task bloccante libera il lock
27
NB: un metodo non synchronized non è mai
28
bloccato.
7
27/03/2015
Commenti su intrinsic lock
Diverse invocazioni di metodi synchronized sullo
stesso oggetto non sono soggette a interleaving
quanto un thread X esegue un metodo synchronized di un oggetto, i
thread che eseguono invocazioni di metodi synchronized sullo stesso
oggetto vengono sospesi fino a che X termina l'esecuzione del
metodo
Quando un metodo synchronized termina, stabilisce
automaticamente una relazione happens-before con
ogni invocazione successiva di metodi synchronized
sullo stesso oggetto
NOTA eventuali dati final (che non possono essere
modificati dopo la creazione) possono essere letti con
metodi non synchronized
Ulteriori commenti sul lock
L'intrinsic lock viene acquisito automaticamente
all'invocazione di un metodo synchronized e
rilasciato al ritorno (normale o eccezionale o da
uncaught exception)
NB: Caso di invocazione di metodo static
synchronized:
Il thread acquisisce l'intrinsic lock per il Class object
associato alla classe. Pertanto l'accesso ai campi
static è controllato da un lock speciale, diverso da
quelli associati alle istanze della classe
29
30
Costruttori non synchronized
I costruttori non possono essere synchronized.
Motivo: Solo il thread che crea l'oggetto può avere accesso ad esso
mentre viene creato, in quanto l'oggetto non viene normalmente
mostrato prima che la sua costruzione e l'inizializzazione siano
completate. Non vi è quindi ragione di rendere il costruttore
synchronized.
Gli altri threads non possono vedere l'oggetto creato fino a che la
costruzione non è completa, a meno non il costruttore non «faccia
uscire» un riferimento all'oggetto durante la costruzione
per esempio generare una lista instances di oggetti creati mediante
l'istruzione instances.add(this), contenuta nel costruttore (che rende visibile
l'oggetto o sue parti prima che la costruzione sia completata.
Questo modo di procedere è comunque da evitare
Se proprio necessario ci sono modi per «bloccare» le parti del costruttore
che restituiscono l'ogetto
Come evitare deadlock, starvation e livelock
LIVENESS
8
27/03/2015
Deadlock
Liveness
Un'applicazione concorrente deve essere eseguita
entro accettabili limiti di tempo
E' una proprietà molto importante in pratica
Può essere violata per «colpa» dei metodi
synchronized
Principali situazioni da evitare attraverso un'attenta
progettazione
deadlock, starvation e livelock
Un modo per evitare queste situazioni è ridurre l'uso di
metodi synchronized, introducendo dei lock «piu' fini» a
livello di singole istruzioni
Due e più thread sono bloccati per
sempre, in attesa l'uno dell'altro
Esempio: Al e Joe sono amici e
credono nel galateo, che dice che se
una persona si inchina a un amico, deve
restare inchinata fino a che l'amico
restituisce l'inchino
Problema: inchino reciproco allo stesso
tempo
33
34
http://docs.oracle.com/javase/tutorial/essential/concurrency/deadlock.html
public class Friend {
private final String name;
public Friend(String name) {this.name = name;
public String getName() {return this.name;
}
}
public synchronized void bow(Friend bower) {
System.out.format("%s: %s" + " has bowed to me!%n",
this.name, bower.getName());
bower.bowBack(this); //ASPETTA RESTITUZIONE INCHINO
}
public synchronized void bowBack(Friend bower) {
System.out.format("%s: %s" + " has bowed back to me!%n",
this.name, bower.getName());
}
}
final Friend al= new Friend("Al");
final Friend joe= new Friend("Joe");
new Thread(new Runnable() {public void run() {al.bow(joe);}}).start();
35
new Thread(new Runnable() {public void run() {joe.bow(al);}}).start();
Starvation
Situazione in cui un thread ha difficoltà a
guadagnare accesso a una risorsa condivisa
e quindi ha difficoltà a procedere
Esempi:
altri task ingordi ( greedy ) che molto
frequentemente invocano metodi lunghi ritardano
costantemente il thread,
uno scheduler che usa priorità cede sempre
precedenza a task greedy che hanno alta priorità
Caso più raro del deadlock, ma può
36
9
27/03/2015
Synchronized statements
Livelock
Errore di progetto che genera una
sequenza ciclica di operazioni inutili ai
fini dell'effettivo avanzamento della
computazione
Esempio:
La sequenza infinita di vada prima lei
E' un caso abbastanza raro, ma possibile.
37
Synchronized statements su oggetti
diversi da this
Es. classe XXX ha 2 campi c1 e c2, mai
usati assieme.
I loro update devono essere
synchronized, ma non c'e motivo
di impedire di farne interleaving:
si ridurrebbe la concorrenza con
blocchi non necessari
public class XXX {
private long c1 = 0;
private long c2 = 0;
final private Object lock1 = new
Object();
final private Object lock2 = new
Object();
public void inc1() {
synchronized(lock1) {
c1++;
}
}
public void inc2() {
synchronized(lock2) {
c2++;
}
39
}
invece di usare synchronized
methods o sync. Statement su this,
si creano due oggetti al solo scopo di
fornire un lock
lock1 usato per bloccare c1, lock per
c2. Quindi ogni volta che si deve
usare c1 occorre bloccare lock1.
Utili per ridurre problemi di liveness dovuti a synchronized
methods
Devono specificare un oggetto a cui applicare il lock, qui
this
public void addName(String name) {
synchronized(this) {
lastName = name;
nameCount++;
}
nameList.add(name);
}
in questo modo si rilascia il lock sull'oggetto this prima di
invocare metodo nameList.add, che potrebbe a sua volta
richiedere di attendere il rilascio di un lock (potenziali
38
problemi di liveness, come nel caso dell'inchino)
Synchronized statements:
controllo fine della concorrenza
Vantaggio: diminuiscono le parti di codice che devono
essere synchronized
Minore possibilità di deadlock
Codice più efficiente
Utile in pratica, ma richiede MOLTA attenzione: controllo
così fine spesso induce errori di programmazione, o
comunque risulta molto complesso
Nell'es., siamo proprio sicuri che possiamo lasciare
interleaving degli update di c1 e c2?
Se usiamo c1 e ci dimentichiamo di fare il lock?
Vedremo alternative più avanti
40
}
10
27/03/2015
Precondizioni per metodi
synchronized: guarded blocks
Come evitare il prelievo se il conto va "in
rosso"?
class ContoCorrente {
private float saldo;
WAIT E NOTIFY
synchronized public void prelievo (float soldi) {
while (saldo-soldi<0) wait();
saldo -= soldi;
}
rilascia il lock sull'oggetto e sospende il
42
task («va in wait»)
Come risvegliare un task in wait?
class ContoCorrente {
private float saldo;
public ContoCorrente (float saldoIniz)
{ saldo = saldoIniz; }
synchronized public void deposito (float soldi) {
saldo += soldi;
risveglia un task sospeso
notify();
in wait, se esiste nondeterminismo
}
synchronized public void prelievo (float soldi) {
while (saldo-soldi<0) wait();
saldo -= soldi;
}
Attenzione!
if (saldo-soldi<0) wait();
Non è sufficiente: il saldo potrebbe non
43
essere diventato positivo
Ciclo di vita «completo» di un
thread
born
start
notify
notifyAll
ready
scheduler
wait
running
I/O
lock
waiting
fine I/O
fine lock
blocked
dead
44
11
27/03/2015
Attenzione ai dettagli
Ancora dettagli
Un thread che e' andato in wait puo' essere
risvegliato solo da un notify o notifyAll
Chiamando wait() occorre in realtà anche catturare
l'eccezione checked InterruptedException (lanciata
se un altro thread invoca metodo interrupt() sul thread
in sospeso, ad es. perche' esecuzione del programma
viene abortita)
La soluzione più semplice (ma non sempre la
migliore) è aggiungere clausola throws
InterruptedException al metodo che chiama la wait.
Esempio:
synchronized public void prelievo (float soldi) throws
InterruptedException {
while (saldo-soldi<0) wait();
saldo -= soldi;
}
altrimenti resta in wait per sempre
E' un caso diverso dal thread che si è bloccato perche'
chiama un metodo synchronized su un oggetto locked
Questo infatti procederà quando l'oggetto sara' unlocked,
senza bisogno di notify.
Quando risvegliato, il thread «compete» con altri
thread in attesa ma che non erano in wait
Esempio: una coda fifo condivisa
Schema produttore/consumatore
con buffer unitario
Operazione di inserimento di elemento:
sospende task se coda piena
while (codaPiena()) wait();
al termine
notify();
Operazione di estrazione di elemento:
sospende task se coda vuota
while (codaVuota()) wait();
al termine
notify();
invece notifyAll risveglia tutti i
task in sospeso
MA uno solo guadagna
il lock
47
public synchronized void put(String message) {
public synchronized String take() {
// Wait until message has
// Wait until message is
// been retrieved.
// available.
while (!empty) {
while (empty) {
try {
try {
wait();
wait();
} catch (InterruptedException e) {}
} catch (InterruptedException e) {}
}
}
// Toggle status.
// Toggle status.
empty = false;
empty = true;
// Store message.
this.message = message;
// Notify producer that
// Notify consumer that status
// status has changed.
// has changed.
notifyAll();
notifyAll();
return message;
48
}
}
12
27/03/2015
Differenza notify - notifyAll
Nel dubbio, usare notifyAll (che però e' meno
efficiente perche' risveglia TUTTI i thread in
wait)
Notify risveglia un solo thread.
Utile quando sappiamo che ce n'e' uno solo in attesa
o se non importa quale deve procedere.
Es: il prod/cons con un solo thread producer e un
solo thread consumer: basta notify
Se pero' molti prod e molti cons con notify si può
avere deadlock: necessario notifyAll
deadlock perche' ci possono essere threads non in wait
che bloccano il buffer e impediscono al thread in wait di
proseguire
Synchronized in sola lettura?
Per consentire ottimizzazioni e caching di parte della JVM,
accesso concorrente anche in sola lettura richiede un lock
JVM può memorizzare in una cache locale al singolo thread il valore di
ogni variabile (es. threads che stanno su core diversi)
JVM (per motivi di ottimizzazione, a seconda dell architettura dello
HW) può riordinare, anticipare o ritardare l esecuzione di alcune
istruzioni, fra cui l update di una variabile:
Deadlock producer/consumer con notify
3 threads P1,P2,P3 produttori; C1,C2 C3 consumatori, un Buffer b.
(leggono/scrivono sullo stesso Buffer)
P1: b.put(..), che scrive ed esce.
P2: b.put(..) e va in wait (b pieno)
P3: b.put(..) e va in wait (b pieno)
C1: b.take(), che comincia l'esecuzione. Ora b è locked
C2, C3: b.take(): b e' locked, quindi si bloccano
C1 finisce di eseguire b.take(..), che chiama notify ed esce
La notify risveglia P2, ma C2 entra prima di P2 e fa lock di b: P2 si
blocca (NON in wait)
8. Ma C2 va in wait perche' b vuoto
9. C3 si sblocca ed esegue b.take(), ma b vuoto: wait
1.
2.
3.
4.
5.
6.
7.
ORA P3, C2, e C3 SONO IN WAIT
10.P2 acquisisce lock, esegue b.put(..) che fa notify ed esce.
11.Il notify risveglia P3, che pero' va in wait perche' buffer non vuoto
12.DEADLOCK: 3 thread in wait, e nessun altro thread che faccia notify!
volatile (cenni)
Alternativa a sincronizzazione in lettura: dichiarare la variabile
«volatile»
private volatile int x;
Utile SOLO quando ci sono alcuni threads che leggono
soltanto, ma che non scrivono mai.
P. es. un costruttore di solito ritorna il reference prima di avere inizializzato l oggetto!
Allora la lettura non sincronizzata di una variabile può essere del tutto
errata. Un thread non sincronizzato:
continua a usare il valore nella cache locale, senza mai accorgersi che altri thread
hanno modificato il valore della variabile.
Perde gli update di un altro thread in esecuzione che sono stati ritardati.
Un thread synchronized quando prende/rilascia il lock forza tutti gli
update, sincronizzando anche la propria cache con la memoria
principale.
La lettura o scrittura di x non può essere riordinata dalla JVM
Tutte le istruzioni che seguono nel thread il read/write di x sono
eseguite dopo il read/write, tutte quelle che precedono sono
eseguite prima.
La lettura di x è atomica e avviene dalla memoria principale
La scrittura della variabile forzala sincronizzazione della cache
locale del thread con la memoria principale
Si perdono ottimizzazioni del compilatore, con diminuzione di
efficienza
13
27/03/2015
Problemi con oggetti mutabili
D a http://docs.oracle.com/jav ase/tutorial/essential/concurrency/syncrgb.html
public class SynchronizedRGB {
// Values must be between 0 and 255.
private int red;
private int green;
private int blue;
private String name;
private void check(int red,
int green,int blue) {
if (red < 0 || red > 255
|| green < 0 || green > 255
|| blue < 0 || blue > 255) {
throw new IllegalArgumentException();
}
}
public void set(int red,
int green,int blue, String name) {
check(red, green, blue);
synchronized (this) {
this.red = red;
this.green = green;
this.blue = blue;
this.name = name;
}
}
Possibile inconsistenza
SynchronizedRGB color = new SynchronizedRGB(0, 0, 0, "Pitch Black");
int myColorInt = color.getRGB();
//Statement 1
String myColorName = color.getName(); //Statement 2
Se un altro thread invoca color.set dopo Statement 1 ma prima di Statement 2, il
valore di myColorInt non corrisponde al valore di myColorName.
Fix: legare assieme i due statement in un unico synchronized statement
public synchronized int getRGB() {
//calcola intero che rappresenta il colore con manipolazione
// rappresentazioni binarie: bitwise inclusive or | e shift sx <<
||es. 4|1 dà 5, << X esegue shift a sinistra di X posizioni
return ((red << 16) | (green << 8) | blue);
}
public synchronized String getName(){
return name;
}
public SynchronizedRGB(int red,
int green, int blue, String name) {
check(red, green, blue);
this.red = red;
this.green = green;
this.blue = blue;
this.name = name;
}
public synchronized void invert() {
red = 255 - red;
green = 255 - green;
blue = 255 - blue;
name = "Inverse of " + name;
53
}
}
Oggetti immutabili
Oggetti immutabili: sono quelli che non possono
cambiare stato
Non sono quindi soggetti a interferenza ne' osservabili
in uno stato inconsistente
Programmatori spesso riluttanti a usarli perché poco
efficienti , MA:
il costo di creazione di nuovi oggetti al posto della loro
modifica è spesso sovrastimato (e alto per collezioni, ma basso
per oggetti semplici come RGB dell esempio).
overhead dovuto a garbage collection si riduce
si elimina codice necessario a proteggere oggetti mutabili da
modifiche non volute e da multithreading.
Maximum reliance on immutable objects is widely
accepted as a sound strategy for creating simple,
reliable code .
synchronized (color) {
int myColorInt = color.getRGB();
String myColorName = color.getName();
}
Quindi oltre ad avere dichiarato synchronized vari metodi e statement di
SynchronizedRGB, dobbiamo aggiungere ulteriori sync. nel codice che usa la
classe!
Ma abbiamo già visto che molty sync. Stat. e meth. possono dare problemi
Il problema può sorgere solo perché l'oggetto è mutabile:54si può
definire un SynchronizedRGB immutabile
Come creare oggetti immutabili
Non fornire metodi "setter"
Definire tutti gli attributi di istanza final e private
Se gli attributi di istanza hanno riferimenti a oggetti mutabili,
non consentire la modifica di questi ultimi:
- Non fornire metodi che modificano oggetti mutabili
- Non fare sharing di ref. a oggetti mutabili. Non salvare
ref. a oggetti esterni mutabili passati al costruttore, se
necessario fare copie e salvare ref. alle copie. Inoltre
creare copie degli oggetti interni mutabili se necessario
per evitare di restituire gli originali attraverso i metodi
- per garantire imutabilità può essere necessario anche non
consentire alle sottoclassi di fare override dei metodi (p.es.
dichiarando la classe final), dichiarare il costruttore private e
56
costruendo gli oggetti mediante metodi statici, detti factory methods
14
27/03/2015
Soluzione ImmutableRGB
Esempio ImmutableRGB
final public class ImmutableRGB {
Nella versione mutabile ci sono due metodi «setter». Il
primo (set) trasforma arbitrariamente l'oggetto e non
avrà alcun corrispettivo nella versione immutabile. Il
secondo, invert, viene adattato creando un nuovo
oggetto invece di modificare l'oggetto corrente
Tutti gli attributi sono già private; vengono
ulteriormente qualificati final
La classe viene qualificata final
Un solo attributo fa riferimento a un oggetto, e
l'oggetto è immutabile. Non è quindi necessario far
nulla per salvaguardare lo stato di eventuali oggetti
mutabili contenuti
57
Concetti avanzati:
java.util.concurrent
Finora abbiamo visto i concetti di base, che però risultano
inadeguati a un'effettiva programmazione concorrente
Sono «di basso livello» e richiedono al programmatore di scendere
troppo nei dettagli, rendendo i programmi difficile da scrivere e
mantenere.
Vediamo ora concetti introdotti con Java 5.0, in particolare
nei packages java.util.concurrent
Oggetti lock
Esecutori
Collezioni concorrenti
Variabili atomiche
Il suggerimento è di usare questi strumenti al
posto di wait e notify non appena possibile
59
// Values must be between 0 and 255.
final private int red;
final private int green;
final private int blue;
final private String name;
private void check(int red,
int green,
int blue) {
if (red < 0 || red > 255
|| green < 0 || green > 255
|| blue < 0 || blue > 255) {
throw new IllegalArgumentException();
}
}
public ImmutableRGB(int red,
int green,
int blue,
String name) {
check(red, green, blue);
this.red = red;
this.green = green;
this.blue = blue;
this.name = name;
}
Oggetti lock
public int getRGB() {
return ((red << 16) |
(green << 8) | blue);
}
public String getName() {
return name;
}
public ImmutableRGB invert() {
return new ImmutableRGB(255 - red,
255 - green,
255 - blue,
"Inverse of " + name);
}
}
58
Lock objects
Il codice synchronized definisce un caso elementare di lock
(implicito), ma meccanismi più sofisticati sono forniti dal
package java.util.concurrent.locks
Un lock, definito dall'interfaccia lock, può essere acquisito
da un solo thread, come nel caso degli implicit lock
associati a codice synchronized
E' pero possibile ritirarsi dalla richiesta di lock
il metodo trylock esce dal lock se questo non è
disponibile (immediatamente o dopo un timeout da
specificare)
il metodo lockInterruptibly consente il ritiro se un altro
thread manda un interrupt prima che il lock sia acquisito
60
15
27/03/2015
Friend: bow e bowBack
Esempio: Al, Joe e l'inchino
class Friend {
1: Creazione dei thread
oggetti Friend devono acquisire i locks di entrambi
i partecipanti prima di proseguire con l'inchino.
public static void main(String[] args) {
final Friend joe= new Friend("Joe");
final Friend al= new Friend("Al");
new Thread(new BowLoop(joe, al)).start();
new Thread(new BowLoop(al, joe)).start();
}
BowLoop e' una classe di prova che fa inchinare per sempre Al e
Joe ad intervalli d tempo casuali
61
Friend: l'attributo lock e il metodo
impendingBow
class Friend {
private final Lock lock = new ReentrantLock(); //attributo per fare lock
public boolean impendingBow(Friend bower) {
Boolean myLock = false;
Boolean yourLock = false;
try {
myLock = lock.tryLock(); //cerca di bloccare this.lock
yourLock = bower.lock.tryLock(); //cerca di bloccare bower.lock
} finally {
if (! (myLock && yourLock)) {//se non ha entrambi i lock, rilascia
if (myLock) {
lock.unlock();
}
if (yourLock) {
bower.lock.unlock();
}
}
}
63
return myLock && yourLock;
}
}
restituisce vero se riesce ad acquisire
il lock di ciascuno dei due attori che consentono
all'uno di inchinarsi e all'altro di restituire l'inchino
public void bow(Friend bower) {
if (impendingBow(bower)) {
try {
System.out.format("%s: %s has + " bowed to me!%n", this.name, bower.getName());
bower.bowBack(this);
} finally {//in uscita si rilasciano i lock di this e del bower:
lock.unlock();
bower.lock.unlock();
}
} else {
System.out.format("%s: %s started« + " to bow to me, but saw that"
+ " I was already bowing to him.%n , this.name, bower.getName());
}
}
public void bowBack(Friend bower) {
System.out.format("%s: %s has" + " bowed back to me!%n",
this.name, bower.getName());
}
62
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.Random;
public class Safelock {
static class Friend {
private final String name;
private final Lock lock = new ReentrantLock();
public Friend(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public boolean impendingBow(Friend bower) {
Boolean myLock = false;
Boolean yourLock = false;
try {
myLock = lock.tryLock();
yourLock = bower.lock.tryLock();
} finally {
if (! (myLock && yourLock)) {
if (myLock) {
lock.unlock();
}
if (yourLock) {
bower.lock.unlock();
}
}
}
return myLock && yourLock;
}
Codice
completo
PARTE 1
64
16
27/03/2015
public void bow(Friend bower) {
if (impendingBow(bower)) {
try {
System.out.format("%s: %s has"
+ " bowed to me!%n",
this.name, bower.getName());
bower.bowBack(this);
} finally {
lock.unlock();
bower.lock.unlock();
}
} else {
System.out.format("%s: %s started"
+ " to bow to me, but saw that"
+ " I was already bowing to"
+ " him.%n",
this.name, bower.getName());
}
}
static class BowLoop implements Runnable {
private Friend bower;
private Friend bowee;
public BowLoop(Friend bower, Friend bowee) {
this.bower = bower;
this.bowee = bowee;
}
public void run() {
Random random = new Random();
for (;;) {
try {
Thread.sleep(random.nextInt(10));
} catch (InterruptedException e) {}
bowee.bow(bower);
}
}
Codice
completo
PARTE 2
Codice
completo
PARTE 3
}
public void bowBack(Friend bower) {
System.out.format("%s: %s has" +
" bowed back to me!%n",
this.name, bower.getName());
}
public static void main(String[] args) {
final Friend al =
new Friend("Al");
final Friend joe =
new Friend(«Joe");
new Thread(new BowLoop(al, joe)).start();
new Thread(new BowLoop(joe, al)).start();
}
}
65
}
66
Esecutori
Gli strumenti finora disponibili impongono una
stretta relazione tra il compito che deve essere
eseguito da un thread (definito dall'oggetto
Runnable e il thread stesso, definito dall'oggetto
Thread
I due concetti possono essere tenuti distinti in
applicazioni complesse, mediante
interfacce Executor
Implementazione con thread pools
Implementazione con fork/join
Gli esecutori sono predefiniti e consentono una
gestione efficiente che riduce il pesanti
overhead dovuto alla gestione dei thread67
Thread Pools
Utili per limitare il numero di thread in esecuzione allo
stesso tempo.
sovraccarico delle prestazioni associato ad inizializzare un nuovo
thread, assegnargli memoria memoria per lo stack ecc
Un server che riceve continuamente richieste potrebbe bloccarsi
completamente
Invece di avviare un nuovo thread per ogni task, una
task è passata a un thread pool.
Non appena il thread pool ha qualche thread inattivo, il
task gli viene assegnato ed eseguito.
Uso tipico per i server: ogni connessione in entrata è
passata al pool.
17
27/03/2015
Interfacce Executors
Il package java.util.concurrent definisce 3
interfacce
Executor
ExecutorService (estende Executor)
ScheduledExecutorService (estende ExecutorService)
Utilizzo di Executor
Se r è un Runnable ed e è un Executor, invece di
(new Thread(r)).start();
facciamo
e.execute(r);
evitando l'overhead dovuto alla creazione degli oggetti
thread
69
Cenno al Fork/Join framework
E' un'altra implementazione di
ExecutorService, utile nel caso di più
processori
Consente di distribuire task a elementi
di un thread pool secondo uno schema
ricorsivo:
if (il mio task è abbastanza piccolo)
eseguo direttamente
else
spezzo il mio task in due task
invoco i due pezzi e attendo il risultato
Implementazione di Executors
Le implementazioni dell'interfaccia
Executors usano thread pools, che
consistono di thread che esistono al di
fuori di Runnable
Un esempio comune è l'implementazione di Executor
che usa un fixed thread pool , che viene creato
chiamando il factory method newFixedThreadPool della
classe java.util.concurrent.Executors
I task sono inviati al pool attraverso una
coda (BlockingQueue)
70
Collezione concorrenti
Molte collezioni «standard» di Java non sono
synchronized, per motivi di efficienza
Se serve usare collezioni in ambito multithread,
occorrebbe
1) Definire le proprie collez. synchronized, oppure
2) Usare le collezioni synchronized previste dal package
java.util.collections. Ora nuove collezioni in
java.util.concurrent
Es. ArrayList NON è synchronized: non adatto a multithread
ing. Si può definire una versione sincronizzata così:
List m = Collections.synchronizedList(new ArrayList());
71
18
27/03/2015
java.util.concurrent
Variabili atomiche
BlockingQueue
struttura dati FIFO che blocca thread se si cerca di inserire in
coda piena o estrarre da coda vuota
Utile come buffer per produttore/consumatore
ConcurrentHashMap (implementa ConcurrentMap)
Rende atomiche le operazioni di 1) eliminazione/modifica di
una coppia chiave-valore solo se chiave presente e 2)
aggiunta chiave-valore solo se assente
Molto efficiente perche 1) evita di sincronizzare l intera hash
map, ma solo la porzione che serve e 2) la lettura dei dati
avviene senza lock.
Il package java.util.atomic definisce classi che
supportano operazioni atomiche su singole
variabili
Esse posseggono tutte metodi get e set che si
comportano come lettura e scrittura delle
corrispondenti variabili non atomiche
E' disponibile anche un'operazione
compareAndSet
73
Esempio
class Counter {
private int c = 0;
class SynchronizedCounter {
private int c = 0;
public synchronized void increment() {
c++;
public void increment() {
c++;
}
}
public void decrement() {
c--;
}
public synchronized void decrement() {
c--;
}
public int value() {
return c;
}
public synchronized int value() {
return c;
}
}
}
PROBLEMI DI INTERFERENZA!
NO INTERFERENZA MA POTENZIALI
DEADLOCK!
Alternativa dei synchronized statement è soluzione molto «fine» che
75
porta facilmente a errori. Cosa fare?
74
Esempio con Oggetti atomici
import java.util.concurrent.atomic.AtomicInteger;
class AtomicCounter {
private AtomicInteger c = new AtomicInteger(0);
public void increment() {
c.incrementAndGet();
}
public void decrement() {
c.decrementAndGet();
}
public int value() {
return c.get();
}
Consentono di evitare i problemi di liveness che possono
essere causati dall'uso dei metodi synchronized, senza le
76
complicazioni dei syncronized statements
19
27/03/2015
Array Atomici
Java 5 fornisce anche implementazioni
atomiche di array:
AtomicIntegerArray, AtomicLongArray and
AtomicReferenceArray
Metodi per accesso atomico a un elemento
dell array:
compareAndSet(int indice),
incrementAndGet(int indice)
Programmazione distribuita
78
Obiettivo
Nodi fisici e nodi logici
La rete come architettura fisica
programmabile
Poter allocare computazioni su nodi
fisici diversi e consentire il
coordinamento tra le computazioni sui
diversi nodi
79
Occorre distinguere tra nodi fisici e logici
Può essere opportuno progettare
ignorando all'inizio il nodo fisico in cui un
nodo logico sara' allocato
Java consente di addirittura di vedere
tutto attraverso la nozione di oggetti e di
invocazione di metodi, dove
l'invocazione può essere remota
80
20
27/03/2015
Architettura client-server
Middleware
È il modo classico di progettare applicazioni
distribuite su rete
Server
offre un servizio "centralizzato"
attende che altri (client) li contattino per ottenere un
servizio
esempio: web server
Client
si rivolge ad apposito/i server per ottenere certi
servizi
esempio: browser www
81
Per programmare un sistema distribuito
vengono forniti servizi specifici, come
estensione del sistema operativo
Il middleware viene posto tra il sistema
operativo e le applicazioni
In Java il middleware fa parte del
linguaggio, diversamente da altri
middleware (es. CORBA)
14
82
15
Socket in Java
Middleware
package java.net, classi: Socket, ServerSocket
Client e server comunicano (pacchetti TCP)
attraverso socket (attacchi)
socket
client
83
socket
server
Endpoint individuati da:
indirizzo IP
numero di porta (uno per ogni applicazione)
84
21
27/03/2015
Comunicazione client-server
Socket come stream
Apertura, lettura e scrittura di socket del tutto
simili a gestione IO con stream
Differenza:
Metodo accept per mettersi in ascolto
Metodo connect per iniziare una connessione
1
2
Un oggetto di tipo Socket e' associato a uno
stream di input, da cui leggere, e uno stream
di output su cui scrivere
3
1. il ServerSocket del server si mette in attesa di una connessione
2. il socket del client si collega al server socket
3. viene creato un socket nel server e quindi stabilito un canale di
comunicazione con i l client
Metodi getInputStream e getOutputStream.
E' possibile stabilire connessioni TCP o UDP.
85
Attesa connessione (lato server)
1. Creare un'istanza della classe java.net.ServerSocket
specificando il numero di porta su cui rimanere in ascolto
ServerSocket serverSocket=new ServerSocket(4567);
2. Chiamare il metodo accept() che fa in modo che il server
rimanga in ascolto di una richiesta di connessione (la
porta non deve essere già in uso)
Socket mioSocket=serverSocket.accept();
3. Quando il metodo completa la sua esecuzione la
connessione col client è stabilita e viene restituita
un'istanza di java.net.Socket connessa al client remoto
87
Attenzione a UDP che non ha gestione degli errori
(puo'
Aprire connessione (lato client)
1. Aprire un socket specificando indirizzo IP e numero di
porta del server
Socket socket = new Socket("127.0.0.1", 4567)
2. All'indirizzo e numero di porta specificati ci deve essere in
ascolto un processo server
Socket socket=serverSocket.accept();
3. Se la connessione ha successo si usano (sia dal lato
client che dal lato server) gli stream associati al socket per
permettere la comunicazione tra client e server (e
viceversa)
Scanner in = new Scanner(socket.getInputStream());
PrintWriter out = new
PrintWriter(socket.getOutputStream());
88
22
27/03/2015
Esempio di server: EchoServer
Si crei un server che accetta
connessioni TCP sulla porta 1337
Una volta accettata la connessione il
server leggerà ciò che viene scritto una
riga alla volta e ripeterà nella stessa
connessione ciò che è stato scritto.
Se il server riceve una riga quit
chiuderà la connessione e terminerà l'
89
esecuzione
Esempio di client: LineClient
Si crei un client che si colleghi, con
protocollo TCP, alla porta 1337
dell'indirizzo IP 127.0.0.1
Una volta stabilita la connessione il client
legge una riga alla volta dallo standard
input e invia il testo digitato al server
Il client inoltre stampa sullo standard
output le risposte ottenute dal server
Il client deve terminare quando il server
91
chiude la connessione
public class EchoServer {
private int port;
public void startServer() throws IOException {
serverSocket = new ServerSocket(port); // apro una porta TCP
System.out.println("Server socket ready on port: " + port);
Socket socket = serverSocket.accept(); // resto in attesa di una connessione
System.out.println("Received client connection");
// apro gli stream di input e output per leggere e scrivere nella connessione appena ricevuta:
Scanner in = new Scanner(socket.getInputStream());
PrintWriter out = new PrintWriter(socket.getOutputStream());
while (true) {// leggo e scrivo nella connessione finche' non ricevo "quit":
String line = in.nextLine();
if (line.equals("quit")) {
break;
} else {
out.println("Received: " + line);
out.flush();
}
}
System.out.println("Closing sockets"); // chiudo gli stream e il socket
in.close();
out.close();
socket.close();
serverSocket.close();
}
90
public class LineClient {
private String ip;
private int port;
public void startClient() throws IOException {
Socket socket = new Socket(ip, port);
System.out.println("Connection established");
Scanner socketIn = new Scanner(socket.getInputStream());
PrintWriter socketOut = new PrintWriter(socket.getOutputStream());
Scanner stdin = new Scanner(System.in);
try {
while (true) {
String inputLine = stdin.nextLine();
socketOut.println(inputLine);
socketOut.flush();
String socketLine = socketIn.nextLine();
System.out.println(socketLine);
}
} catch(NoSuchElementException e) {
System.out.println("Connection closed");
} finally {
stdin.close();
socketIn.close();
socketOut.close();
socket.close();
}
}
92
23
27/03/2015
Creare EchoServer e LineClient
public class EchoServer {
private int port;
private ServerSocket serverSocket;
public EchoServer(int port) {
this.port = port;
}
public static void main(String[] args) {
EchoServer server = new
EchoServer(1337);
try {
server.startServer();
} catch (IOException e) {
System.err.println(e.getMessage());
}
}
}
public class LineClient {
private String ip;
private int port;
public void startClient() throws IOException {
public LineClient(String ip, int port) {
this.ip = ip;
this.port = port;
}
public static void main(String[] args) {
LineClient client = new
LineClient("127.0.0.1", 1337);
try {
client.startClient();
} catch (IOException e) {
System.err.println(e.getMessage());
}
}
}
Serializzazione
E se devo inviare oggetti?
Posso inviare tramite socket anche un oggetto
a patto che sia «serializzabile».
Serializzazione=trasformazione di un oggetto in un byte
stream
Classe dell'oggetto deve implementare
l'interfaccia Serializable
93
Es. serializzazione
ClasseSerializzabile o = new ClasseSerializzabile ();
//Decorazione flusso uscita del socket
ObjectOutputStream objOut = new
ObjectOutputStream(socket.getOutputStream());
//Scrittura dell'oggetto o sul socket socket:
objOut.writeObject(o);
//Decorazione flusso uscita del socket:
ObjectInputStream in = new
ObjectInputStream(socket.getInputStream());
//Lettura di un oggetto dal socket:
o = (ClasseSerializzabile)objInputStream.readObject();
Architettura del server
Il server che abbiamo visto accetta una sola
connessione (da un solo client)
Un server dovrebbe essere in grado di
accettare connessioni da diversi client e di
dialogare con questi contemporaneamente
Idea: server multi-thread
All'interno del processo Server far eseguire
l'istruzione accept() in un nuovo thread
In questo modo è possibile accettare più client
contemporaneamente
96
24
27/03/2015
Esempio: EchoServer multithread
Spostiamo la logica che gestisce la
comunicazione con il client in una nuova
classe ClientHandler che implementa
Runnable
La classe principale del server si occupa solo
di istanziare il ServerSocket, eseguire la
accept() e di creare i thread necessari per
gestire le connessioni accettate
La classe ClientHandler si occupa di gestire la
comunicazione con il client associato al
socket assegnato
97
EchoServer multi-thread (Client handler)
public class EchoServerClientHandler implements Runnable {
private Socket socket;
public EchoServerClientHandler(Socket socket) {
this.socket = socket;
}
public void run() {
try {
Scanner in = new Scanner(socket.getInputStream());
PrintWriter out = new PrintWriter(socket.getOutputStream());
// leggo e scrivo nella connessione finche' non ricevo "quit"
while (true) {
String line = in.nextLine();
if (line.equals("quit")) {
break;
} else {
out.println("Received: " + line);
out.flush();
}
}
// chiudo gli stream e il socket
in.close();
out.close();
socket.close();
} catch (IOException e) {
System.err.println(e.getMessage());
}
}
}
98
EchoServer multi-thread (Server)
public class MultiEchoServer {
private int port;
public MultiEchoServer(int port) {
this.port = port;
}
public void startServer() {
ExecutorService executor = Executors.newCachedThreadPool();
ServerSocket serverSocket;
try {
serverSocket = new ServerSocket(port);
} catch (IOException e) {
System.err.println(e.getMessage()); // porta non disponibile
return;
}
System.out.println("Server ready");
while (true) {
try {
Socket socket = serverSocket.accept();
executor.submit(new EchoServerClientHandler(socket));
} catch(IOException e) {
break; // entrerei qui se serverSocket venisse chiuso
}
}
executor.shutdown();
}
public static void main(String[] args) {
MultiEchoServer echoServer = new MultiEchoServer(1337);
echoServer.startServer();
}
99
}
Chiusura connessioni
Per chiudere un ServerSocket o un Socket si
utilizza il metodo close()
Per ServerSocket, close() fa terminare la
accept() con IOException
Per Socket, close() fa terminare le operazioni
di lettura o scrittura del socket con eccezioni
che dipendono dal tipo di reader/writer
utilizzato
Sia ServerSocket sia Socket hanno un
metodo isClosed() che restituisce vero se il
socket è stato chiuso
100
25
27/03/2015
RMI Remote Method Invocation
Middleware che porta i vantaggi della
programmazione orientata gli oggetti nel
contesto distribuito
Si possono essere invocati metodi su oggetti remoti,
per i quali si ha un riferimento remoto
Aspetti innovativi
passaggio dei parametri per valore (per oggetti NON
REMOTI) e indirizzo (oggetti REMOTI)
possibilità di download automatico dalla rete delle
classi necessarie per la valutazione remota
101
Architettura client-server con RMI
Caso tipico
Server crea oggetti remoti, li rende visibili e
aspetta che i client invochino metodi su di
essi
Client ottiene riferimenti a oggetti remoti e
invoca metodi su di essi
RMI fornisce il meccanismo con cui il
server e i client comunicano per
costituire l'applicazione distribuita
102
Architettura "esterna"
Architettura esterna
viene usato anche un normale server web per caricare
(dinamicamente) le classi (in bytecode), da client a
103
104
26
27/03/2015
Funzionalità di RMI (1)
Funzionalità di RMI (2)
Localizzazione di oggetti remoti
Gli oggetti remoti sono registrati presso un registro
di nomi (rmiregistry) che fornisce una "naming
facility", oppure
Le operazioni passano come parametri e/o
restituiscono riferimenti a oggetti remoti
Comunicazione con oggetti remoti
Client ottiene servizi dal server invocando metodi
di oggetti remoti con una sintassi identica a quella
per gli oggetti locali (residenti sulla stessa JVM),
ma con una semantica un po' diversa
Dynamic class loading
essendo possibile passare oggetti ai metodi
di oggetti remoti, oltre a trasmettere i valori
dei parametri, RMI consente di trasferire il
codice degli oggetti a run time (potrebbe
trattarsi di un nuovo sottotipo)
È un esempio di codice mobile
105
106
Definizioni
Vincoli
Le interfacce che definiscono le
funzionalità degli oggetti remoti (cioè resi
accessibili da altri oggetti non locali
tramite il sistema RMI) devono ereditare
da java.rmi.Remote
I metodi definiti in tali interfacce devono
dichiarare l'eccezione
java.rmi.RemoteException
107
Oggetto remoto: oggetto i cui metodi possono essere
invocati da una JVM diversa da quella in cui l'oggetto
risiede
Interfaccia remota: interfaccia che dichiara quali sono i
metodi che possono essere invocati da una diversa JVM
Server: insieme di uno o più oggetti remoti che,
implementando una o più interfacce remote, offrono delle
risorse (dati e/o procedure) a macchine esterne
distribuite sulla rete
Remote Method Invocation (RMI): invocazione di un
metodo presente in una interfaccia remota implementata
da un oggetto remoto (sintassi di invocazione 108
remota è
identica a quella locale)
27
27/03/2015
Registry
Architettura interna
È un programma che viene lanciato
dalla finestra dei comandi
Il client colloquia con un proxy (ossia un
«rappresentante») locale del server, detto stub
rmiregistry -JDjava.rmi.server.hostname=0.0.0.0
È un programma di rete che ascolta su
una porta, per default "1099
Occorre dargli comando per realizzare il
binding tra l'oggetto locale esportato
come remoto e il nome col quale ad
esso si riferiscono i client
lo stub rappresenta il server sul lato client
implementa l'interfaccia del server
è capace di fare forward di chiamate di metodi
il client ha un riferimento locale all'oggetto stub
Esiste anche un proxy del client sul lato server, detto
skeleton
è una rappresentazione del client
chiama i servizi del server
sa come fare forward dei risultati
lo skeleton ha un riferimento all'oggetto
109
110
Architettura interna
Architettura interna
Il client colloquia con un proxy locale del server,
detto stub
lo stub rappresenta il server sul lato client
implementa l'interfaccia del server
è capace di fare forward di chiamate di metodi
il client ha un riferimento locale all'oggetto stub
Esiste anche un proxy del client sul lato server,
detto skeleton
è una rappresentazione del client
chiama i servizi del server
sa come fare forward dei risultati
lo skeleton ha un riferimento all'oggetto
111
112
28
27/03/2015
Fasi di creazione di un sistema
1. Definire una INTERFACCIA remota per la
comunicazione tra client e server
2. Definire un oggetto remoto e un'applicazione
server
convenzione: stesso nome dell'interf. e suffisso Impl
3. Definire client che usa un riferimento
all'interfaccia remota per accedere all'oggetto
remoto
4. Compilare ed eseguire
113
Creazione di interfaccia remota
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface HelloServer extends Remote {
String sayHello() throws RemoteException;
}
Un'interfaccia remota
1. deve essere pubblica
2. deve estendere java.rmi.Remote
3. ogni metodo deve dichiarare java.rmi. RemoteException
4. ogni oggetto remoto passato come parametro o
valore di ritorno deve essere dichiarato di tipo interfaccia
remota
114
Lato server
Lato server: esempio
1. Istanziare l'oggetto remoto obj che implementa l'interfaccia remota IR.
2. Creare l'oggetto stub usando la classe
java.rmi.server.UnicastRemoteObject:
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.UnicastRemoteObject;
public class HelloServerImpl implements HelloServer {
public S tring sayHello() {
return "Hello, world!";
IR stub = (IR) UnicastRemoteObject.exportObject(obj,
0);
}
3. Registrare lo stub nel "registry"
public static void main(String args[]) {
l'oggetto obj potrebbe avere metodi che restituiscono riferimenti agli altri
oggetti remoti, evitando così di dover passare attraverso il registry per accedere
ad essi.
Registry registry = LocateRegistry.getRegistry();
registry.rebind( NOME , stub);
try {
HelloServerImpl helloServer = new HelloServerImpl(); HelloServer helloServerStub = (HelloServer)
UnicastRemoteObject.exportObject(helloServer, 0);
// Registra il riferimento remoto (stub) dell'helloServer nel
// registry e lo identifica con la stringa "Hello".
Registry registry = LocateRegistry.getRegistry(); registry.rebind("Hello", helloServerStub);
S ystem.err.println("Server ready");
Quando si esegue il server bisogna specificare come parametro della JVM:
-Djava.rmi.server.codebase=file:classDir/ dove classDir è la directory che contiene i file compilati (deve
essere raggiungibile sia dal client che dal registry!). In alternativa bisogna rendere i file compilati disponibili
nel classpath del client e del registry.
115
} catch (Exception e) {
System.err.println("Server exception: " + e.toString());
e.printStackTrace();
}
116
29
27/03/2015
package distrib1.rmi;
evita il rischio di "bind"qualora
import java.rmi.*;
esista già un oggetto
import java.rmi.server.*;
registrato
import java.rmi.registry.*;
con lo stesso nome
import java.net.*;
public class PerfectTimeImpl extends UnicastRemoteObject
implements PerfectTime {
public long getPerfectTime() throws RemoteException {
return System.currentTimeMillis();
}
public PerfectTimeImpl() throws RemoteException {
super(); //inutile, peraltro
}
public static void main (String args[]) throws Exception {
System.setSecurityManager(new RMISecurityManager());
PerfectTime pt = new PerfectTimeImpl();
Naming.rebind ("//localhost/PerfectTimeServer", pt);
System.out.println("Ready to do time");
stringa di registrazione:
}
118
//host:porta/OggettoRem
}
Registry
È un programma che viene lanciato dalla
finestra dei comandi
rmiregistry
È un programma di rete che ascolta su una
porta, per default "1099"
Occorre dargli comando per realizzare il
binding tra il l'oggetto locale esportato come
remoto e il nome col quale ad esso si
riferiscono i client
117
33
se registry sul server: //localhost/OggettoRem
Uso dell'oggetto remoto
Vita degli oggetti remoti
Se main() termina, l'oggetto remoto
registrato continua ad esistere finchè
rmiregistry resta running
Ecco perché è necessario fare rebind
(potrebbe esistere un oggetto con lo
stesso nome)
Si può anche fare
119
package distrib1.rmi;
import java.rmi.*;
import java.rmi.registry.*;
public class DisplayPerfectTime {
public static void main(String[] args) throws Exception {
System.setSecurityManager(new RMISecurityManager());
//lega oggetto locale a remoto
PerfectTime t=(PerfectTime)
Naming.lookup("//localhost/PerfectTimeServer");
for(int i=0; i<10; i++)
System.out.println("Perfect time = "+ t.getPerfectTime());
}
}
34
120
35
30
27/03/2015
Parametri
Serializzazione
Semantica dell'invocazione di un metodo
remoto m(obj)
se obj remoto, normale semantica
se no, m opera su una copia serializzata di
obj; eventuali modifiche allo stato di obj
hanno effetto solo sulla copia, non
sull'originale
in pratica il passaggio di oggetti non remoti è per
valore, mentre per gli oggetti remoti è per
riferimento
121
E` la trasformazione di un oggetto in un byte stream
E` possibile effettuarla su oggetti che implementano
l'interfaccia Serializable
Oggetti passati a, o ritornati da, un oggetto remoto devono
implementare l'interfaccia Serializable
se invece si passano riferimenti remoti, basta implementare
Remote
Stubs e skeletons effettuano automaticamente la
serializzazione e deserializzazione
si dice che fanno il "marshal" (schieramento) e "unmarshal" di
argomenti e risultati
36
122
37
Scaricamento da remoto delle
classi
Che cosa dobbiamo fare noi?
Il tool rmic (rmi compiler) crea i files
necessari per stub e skeleton
Uno dei principi di java è quello dello zero
deployment
Quando si usa RMI è possibile scaricare dal
sito del server:
rmic distrib1.rmi.PerfectTime
genera due classi:
Gli stub degli oggetti remoti
Le classi corrispondenti ai parametri
PerfectTime_Stub.class
PerfectTime_Skel.class
Per esempio, il client riceve un valore di ritorno da una
chiamata remota che ha un certo tipo statico noto al
client, ed un tipo dinamico non noto
(in Java 2 solo stub)
123
38
124
39
31
27/03/2015
Scaricamento da remoto delle
classi
Security manager e file di policy
Le classi possono essere scaricate attraverso
un web server
Per indicare il web server che si utilizzerà, si
lancia il server con il seguente comando:
java -Djava.rmi.server.codebase=
"http://brahms:8000/" PerfectTimeImpl
Perchè client e server possano istanziare
correttamente le classi scaricate, è necessario
configurare il security manager
SecurityManager è una classe in java.lang
che offre metodi per controllare la possibilità
di accesso a varie risorse (file, socket,...)
RMI usa un security manager per controllare i
permessi di uso dei socket da parte di
un'applicazione
Un utente può definire le politiche di sicurezza
editando un file di policy
Questo impedisce alle classi scaricate di eseguire
azioni non legali
125
40
Un esempio di file di policy
connect ;
permission java.net.SocketPermission * ,
accept ;
permission java.io.FilePermission /tmp/* ,
41
Come "far partire" il tutto?
grant {
permission java.net.SocketPermission * ,
126
Far partire il registry
Se necessario, far partire il web server
Far partire il server indicando il file di
policy da usare. Per esempio:
read ;
}
java Djava.security.policy=./distrib1/rmi/permit
distrib1.rmi.PerfectTimeImpl
Far partire il/i client indicando il file di
policy da usare
127
42
128
43
32