new - Dipartimento di Matematica e Informatica

Sincronizzazione dei Thread
Vitaliano Milanese
Dipartimento di Matematica e Informatica
Università di Udine
Programmazione Object Oriented
::
Lezione 22
2
THREAD
Sincronizzazione
Ogni insieme di thread cooperanti nell’ esecuzione di un’ applicazione
possono richiedere di essere sincronizzati
in corrispondenza del verificarsi di due situazioni
• accesso ad una stessa risorsa
• interscambio dati necessari alla prosecuzione dei loro task
Seppure similari
• le due situazioni vanno trattate in modo differenziato
• possono richiedere modelli di sincronizzazione diversificati
3
THREAD
Condivisione di Risorse
Una risorsa condivisa costituisce un’ entità
dotata di meccanismi di protezione all’ accesso da parte dei thread
In Java
il meccanismo di protezione
è fornito da un dispositivo di mutua esclusione (mutex)
Per operare su risorse condivise un thread deve:
• acquisire il mutex della risorsa, bloccandone l’ accesso agli altri thread
• accedere alla risorsa mediante i metodi della stessa
• rilasciare il mutex, liberalizzandone l’ accesso agli altri thread
4
THREAD
Condivisione di Risorse
• ogni oggetto è depositario di un dispositivo di mutex
• in condizioni usuali il mutex di un oggetto è ignorato
Tuttavia
se più thread intendono accedere ai metodi di uno stesso oggetto risorsa
devono gestirne il relativo mutex
• il controllo sul mutex è operato da metodi synchronized
• tutti i metodi della classe devono essere synchronized
Il modificatore synchronized è applicabile a
• metodi dinamici (di uno stesso oggetto)
l’ attivazione di uno di essi blocca le richieste di esecuzione dei rimanenti metodi
quando inoltrate alla stessa istanza
• metodi statici (di una stessa classe)
le esecuzioni sono serializzate in modo che nessuno di essi sia interrotto
dall’ esecuzione dei rimanenti
5
THREAD
Condivisione di Risorse
La risorsa vettoreStati di classe VettoreStatiThread costituisce risorsa condivisa
#1, m
Thd1
#4, k
.. t + dt
vettoreStati
t ..
Thd4
Metodi sincronizzati (accesso via mutex)
Stati generati:
(#1 , m) (#4 , k)
Metodi non sincronizzati (mutex ignorato)
Possibili stati generati:
(#4 , m) (? , k)
6
THREAD
Condivisione di Risorse
DA RICORDARE
• Quandi si utilizza il mutex di un oggetto
i campi dello stesso devono essere dichiarati private
• Se un thread accede ad un metodo sincronizzato di un oggetto
è possibile che quel metodo attivi altri metodi sincronizzati dello stesso oggetto
In questo caso ogni chiamata ad altro metodo
causa un incremento del contatore di lock del mutex
7
THREAD
Sincroniz. per Scambio Dati
La sincronizzazione dei thread è necessaria per serializzare
le operazioni di trasferimento dati fra thread mittente/i e destinatario/i
Il linguaggio
deve disporre di primitive in grado di agire sull’ esecuzione dei thread
forzandone la sospensione e la successiva riattivazione
In Java
• I metodi destinati alla sincronizzazione dei thread sono insiti negli oggetti
• Appartengono alla classe Object da cui derivano le classi di applicazione
8
THREAD
Sincroniz. per Scambio Dati
Dato
(Risorsa)
comunica
Dato
Risorsa
condivisa
sospensione
Sveglia !!!
Thd sospeso
Thd attivo
9
THREAD
Sincroniz. per Scambio Dati
Un thread può portarsi nello stato bloccato (sospeso) per:
• chiamata al metodo sleep()
ritorna runnable a sleeping concluso
oppure per ricezione di un comando interrupt()
Thread interrompibile
• attesa del completamento di un’ operazione di I/O
ritorna runnable a completamento dell’ operazione di I/O
• accesso ad un metodo o ad un blocco synchronized con mutex non disponibile
ritorna runnable a seguito di rilascio del mutex
• chiamata del metodo wait()
Thread interrompibile
ritorna runnable per intercettazione del metodo notify() oppure notifyAll()
10
THREAD
Sincroniz. per Scambio Dati
METODI DI SINCRONIZZAZIONE (Classe Object)
• void wait (long timeout) throws InterruptedException
• void wait() throws InterruptedException
Sospendono il thread correntemente in esecuzione (ev. per il tempo timeout)
Richiedono l’ uso del costrutto try-catch
Un thread sospeso perde il possesso della risorsa su cui opera
e viene inserito nella coda dei processi bloccati e in attesa
pertanto
Le chiamate a wait() vanno incluse in metodi [o regioni] synchronized
I thread sospesi e in attesa del rilascio di una risorsa possono essere risvegliati
da altro thread che opera sulla stessa risorsa con i metodi notify() o notifyAll()
Un thread risvegliato è trasferito nella coda dei processi eseguibili e
compete per ottenere nuovamente il possesso della risorsa condivisa di interesse
11
THREAD
Sincroniz. per Scambio Dati
METODI DI SINCRONIZZAZIONE (Classe Object)
• void notify()
Risveglia un thread che è sospeso in attesa del rilascio di una risorsa
Il thread riottiene il possesso della risorsa
eseguendo un metodo sincronizzato appartenente alla risorsa
• void notifyAll()
Risveglia tutti i thread sospesi in attesa del rilascio di una risorsa
I metodi risvegliati competono per riottenere il controllo della risorsa
in base alla loro priorità e/o al momento della loro sospensione
Il metodo notify() presuppone che tutti i thread sospesi
siano in attesa del verificarsi della stessa condizione
[i.e. siano stati bloccati sullo stesso metodo]
In caso contrario è necessario un controllo esplicito per validare lo stato del thread
12
THREAD
Regioni Critiche
Con il termine
• regione critica
si intende una regione di codice sottoposta a vincoli di sincronizzazione
synchronized (objSinc) {
comandi
}
I comandi sono eseguiti previa acquisizione del mutex di objSinc
da usare quale blocco di sincronizzazione
USO
Rendere sincronizzate operazioni definite da metodi che non sono synchronized
Tipicamente sfruttata per utilizzare in ambito multi-thread classi non protette
rendendole controllate e protette da oggetti di altra classe
13
THREAD
Sincronizzazione
La comunicazione di dati fra thread può seguire modelli diversificati
tra cui:
• produttore/i – consumatore/i
richiede un supporto dati condiviso
più o meno complesso in relazione al numero di thread coinvolti
• monitor
richiede una classe di sincronizzazione
• pipe line
richiede un canale di comunicazione dedicato
14
THREAD
Produttore Consumatore
Schema PRODUTTORE - CONSUMATORE
Uno o pù thread produttori trasferiscono dati
a uno o più thread consumatori (utilizzatori) di quei dati
sfruttando
una risorsa condivisa dotata di metodi sincronizzati
Th1
GUI
Th2
Th3
Th4
Th5
Array di supporto
(condiviso)
Visualizza
le configurazioni
a mano a mano
che sono disponibili
Thread sospeso
e periodicamente
risvegliato
15
THREAD
Produttore Consumatore
Modellazione dei thread produttori di configurazioni:
StatoThread (idThread, countDown)
public class ThreadBanale extends Thread {
private static int threadCount = 0;
private int countDown = 5;
private VettoreStatiThread vettoreStati;
private ThreadGUI threadGUI;
private StatoThread statoThread;
private String idThread;
// Risorsa condivisa
// Thread consumatore
public ThreadBanale (VettoreStatiThread vettoreStati, ThreadGUI threadGUI) {
super ("#" + ++threadCount);
idThread = "#" + threadCount;
this.vettoreStati = vettoreStati;
this.threadGUI = threadGUI;
}
Continua …
16
THREAD
Produttore Consumatore
public class ThreadBanale extends Thread {
……
public void run() {
while (true) {
if (countDown == 0) return;
// Aggiorna l’array comunicando la nuova configurazione di stato
statoThread = new StatoThread (idThread, countDown);
vettoreStati.addStatoThread (statoThread);
// Risveglia il thread di supporto alla GUI
synchronized (threadGUI) {
threadGUI.notify();
}
try { sleep (100); }
catch (InterruptedException e) {}
countDown--;
}
}
}
17
THREAD
Produttore Consumatore
class StatoThread {
private String idThread;
private int countDown;
public StatoThread (String idThread, int countDown) {
this.idThread = idThread;
this.countDown = countDown;
}
public String getIdThread() { return idThread; }
public int getCountDown() { return countDown; }
}
18
THREAD
Produttore Consumatore
class VettoreStatiThread {
Vector<StatoThread> vettoreStati;
public VettoreStatiThread() {
vettoreStati = new Vector<StatoThread> ();
}
public synchronized void addStatoThread (StatoThread statoThread) {
vettoreStati.addElement (statoThread);
}
public synchronized Enumeration getStatiThread() {
Enumeration statiThread = vettoreStati.elements();
vettoreStati = new Vector<StatoThread>();
return statiThread;
}
public synchronized int size() { return vettoreStati.size(); }
}
19
THREAD
Produttore Consumatore
Modellazione del thread consumatore di configurazioni
public class ThreadGUI extends JFrame implements Runnable {
private CtrlThread panThread;
……
private VettoreStatiThread vettoreStati;
// Risorsa condivisa
public ThreadGUI (VettoreStatiThread vettoreStati) {
this.vettoreStati = vettoreStati;
setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
setTitle (getClass().getName());
setSize (hSize, vSize);
Container cnt = getContentPane();
panThread = new CtrlThread();
cnt.add (panThread, "Center");
setVisible (true);
}
Continua …
20
THREAD
Produttore Consumatore
public void run() {
while (true) {
synchronized (this) {
try {
wait();
// Sospensione del thread
}
catch (InterruptedException e) { throw new RuntimeException (e); }
}
Risveglio
del
thread
Enumeration enumStati = vettoreStati.getStatiThread();
while (enumStati.hasMoreElements()) {
StatoThread statoThread = (StatoThread) enumStati.nextElement();
String idThread = statoThread.getIdThread();
int countDown = statoThread.getCountDown();
panThread.drawBox (idThread, countDown);
}
}
}
}
Continua …
21
THREAD
Produttore Consumatore
Modellazione della classe di tracciatura
class CtrlThread extends JPanel {
private int nrBox, countDown, xRef, yRef, delta;
private String idThread;
private Color color;
public CtrlThread() { // Inizializzazione di nrBox, idThread, xRef, yRef, delta; }
public void paintComponent (Graphics g) {
if (nrBox >= 0) {
Graphics2D g2 = (Graphics2D) g;
g2.setColor (color);
g2.fill (new Rectangle2D.Double (xRef, yRef, 30, 30));
g2.setColor (Color.black);
Integer valore = new Integer (countDown);
String str = valore.toString();
g2.drawString (str, xRef + 10, yRef + 20);
}
}
Continua …
22
THREAD
Produttore Consumatore
public void drawBox (String idThread, int countDown) {
this.idThread = idThread;
this.countDown = countDown;
nrBox++;
if ((nrBox % 20 ) == 0) {
xRef = 10;
yRef += delta;
}
else
xRef += delta;
if (idThread.equals ("#1"))
color = Color.green;
……
if (idThread.equals ("#5"))
color = Color.yellow;
// Il metodo repaint() va sostituito da
paintImmediately (new Rectangle (xRef, yRef, 30, 30));
}
}
23
THREAD
Produttore Consumatore
public class Pgm_xThread {
public static void main (String[] args) {
// Costruzione della risorsa condivisa
VettoreStatiThread vettoreStati = new VettoreStatiThread();
// Creazione del thread consumatore
ThreadGUI winRunnable = new ThreadGUI (vettoreStati);
Thread winThread = new Thread (winRunnable);
winThread.start();
// Creazione dei thread produttori
for (int k = 0; k < 5; k++) {
ThreadBanale thread = new ThreadBanale (vettoreStati, winRunnable);
thread.start();
}
}
}
24
THREAD
Produttore Consumatore
DA OSSERVARE
Qualora la classe VettoreStatiThread venisse sostituita da quella sotto indicata
class StatoAccessibile {
StatoThread statoThread;
public synchronized void addStatoThread (StatoThread statoThread) {
this.statoThread = statoThread;
}
public synchronized StatoThread getStatoThread() { return statoThread; }
}
il thread consumatore acquisirebbe soltanto parte degli stati generati dai produttori
25
THREAD
Monitor
Schema MONITOR
Uno o più thread produttori trasferiscono dati a uno o più thread consumatori
sfruttando
una risorsa condivisa (monitor) dotata di metodi sincronizzati
avente la funzione di acquisire e smistare i singoli dati trasferiti
A differenza dello schema precedente
• il monitor non fornisce un mero supporto di allocazione
ma
• svolge funzione attiva
acquisendo dai produttori singoli blocchi di dati solo quando disponibile
e smistando tali dati quando posseduti
26
THREAD
Monitor
PROBLEMA
Due thread produttori di dischi (rispettivamente blu e rossi) li devono trasferire ad
un thread consumatore con cadenze temporali prestabilite
TP1
TP2
Monitor
Il monitor
• se vuoto
TCons
acquisisce un dato dal primo thread mittente pronto a trasmettere
cambia il proprio stato
notifica agli altri thread la variazione di stato, eventualmente risvegliandoli
• se pieno
smista il dato posseduto al thread destinatario
cambia il proprio stato
notifica agli altri thread la variazione di stato, eventualmente risvegliandoli
27
THREAD
Monitor
28
THREAD
Monitor
public class Monitor {
private boolean pieno = false;
private Cerchio cerchio;
public synchronized void ricezione (Cerchio c) {
if (pieno)
try {
wait(); // Il produttore che ha acquisito il monitor viene sospeso
}
catch (InterruptedException e) { }
pieno = true;
cerchio = c;
notifyAll(); // Comunica la variazione di stato
}
Continua …
29
THREAD
Monitor
Segue
public synchronized Cerchio rilascio() {
if (!pieno)
try {
wait(); // Il consumatore che ha acquisito il monitor viene sospeso
}
catch (InterruptedException e) { }
pieno = false;
notifyAll(); // Comunica la variazione di stato
return cerchio;
}
}
30
THREAD
public class Produttore implements Runnable {
private Monitor monitor;
private int rgn;
private Frame_xThread frame;
……
public Produttore (Monitor m, int rgn, Frame_xThread frame) {
monitor = m;
this.rgn = rgn;
this.frame = frame;
switch (rgn) {
case 1: coRef = Color.blue;
nMaxCerchi = 8;
break;
case 2: coRef = Color.red;
nMaxCerchi = 5;
break;
}
vtProd = new Vector<Cerchio>();
for (int k = 1; k <= nMaxCerchi; k++) {
cerchio = new Cerchio (0, 0, 10, coRef);
vtProd.addElement (cerchio);
}
Monitor
31
THREAD
Segue
public void run() {
for (int k = 0; k < nMaxCerchi; k++) {
try {
if (rgn == 1)
Thread.sleep (700);
if (rgn == 2)
Thread.sleep (2000);
}
catch (InterruptedException e) { }
cerchio = vtProd.elementAt (0);
monitor.ricezione (cerchio);
vtProd.removeElementAt (0);
frame.modificaRegione (rgn, nMaxCerchi, vtProd);
}
}
public Vector<Cerchio> getElements() { return vtProd; }
}
Monitor
32
THREAD
public class Consumatore implements Runnable {
private Monitor monitor;
private Frame_xThread frame;
private Vector<Cerchio> vtCons;
public Consumatore (Monitor m, Frame_xThread frame) {
monitor = m;
this.frame = frame;
vtCons = new Vector<Cerchio>();
}
public void run() {
while (true) {
Cerchio cerchio = monitor.rilascio();
vtCons.addElement (cerchio);
int nCerchi = vtCons.size();
frame.modificaRegione (0, nCerchi, vtCons);
}
}
}
Monitor
33
THREAD
Monitor
public class Frame_xThread extends JFrame {
private final int RGN_CONS = 0, RGN_PROD1 = 1, RGN_PROD2 = 2;
private RgnFrame rgnCons, rgnProd1, rgnProd2;
public Frame_xThread () {
super ("Produttori - Consumatore");
……
cnt.setLayout (new GridLayout (3, 1, 5, 5));
rgnProd1 = new RgnFrame();
rgnProd1.setBackground (Color.yellow);
cnt.add (rgnProd1);
rgnProd2 = new RgnFrame();
rgnProd2.setBackground (Color.yellow);
add (rgnProd2);
rgnCons = new RgnFrame();
rgnCons.setBackground (Color.lightGray);
add (rgnCons);
setVisible (true);
}
Continua …
34
THREAD
Monitor
public void modificaRegione (int rgn, int nMaxCerchi, Vector<Cerchio> vt) {
switch (rgn) {
case 0: rgnCons.setElements (vt, nMaxCerchi);
rgnCons.repaint();
break;
case 1: rgnProd1.setElements (vt, nMaxCerchi);
rgnProd1.repaint();
break;
case 2: rgnProd2.setElements (vt, nMaxCerchi);
rgnProd2.repaint();
break;
}
}
}
Continua …
35
THREAD
Monitor
class RgnFrame extends JPanel {
private int nMaxCerchi;
private Vector<Cerchio> vt;
public RgnFrame() {
super();
vt = new Vector<Cerchio>();
nMaxCerchi = 0;
}
public void setElements (Vector<Cerchio> vt, int nMaxCerchi) {
this.vt = vt;
this.nMaxCerchi = nMaxCerchi;
}
Continua …
36
THREAD
Monitor
public void paintComponent (Graphics g) {
super.paintComponent (g);
Graphics2D g2 = (Graphics2D) g;
final int cBaseX = -10;
final int cBaseY = 30;
int cX, cY, rg;
Color colore;
final int rgBase = 10;
if (nMaxCerchi != 0) {
cX = cBaseX;
cY = cBaseY;
for (int k = 0; k < vt.size(); k++) {
Cerchio cRef = vt.elementAt (k);
rg = cRef.raggio;
g2.setColor (cRef.colore);
cX = cX + 4*rg;
g2.fill (new Ellipse2D.Double (cX - rg, cY - rg, 2*rg, 2*rg));
}
g2.setColor (Color.lightGray);
rg = rgBase;
for (int k = vt.size() + 1; k < nMaxCerchi+1; k++) {
cX = cX + 4*rg;
g2.draw (new Ellipse2D.Double (cX - rg, cY - rg, 2*rg, 2*rg));
}
}
37
THREAD
Monitor
public static void main (String[] args) {
Frame_xThread winThread = new Frame_xThread();
Monitor monitor = new Monitor();
// Creazione del primo produttore
Produttore prod1 = new Produttore (monitor, 1, winThread);
Thread thProd1 = new Thread (prod1);
Vector<Cerchio> vtProd1 = new Vector<Cerchio>();
vtProd1 = prod1.getElements();
int nCerchiRgnProd1 = vtProd1.size();
winThread.modificaRegione (1, nCerchiRgnProd1, vtProd1);
// Creazione del secondo produttore
Produttore prod2 = new Produttore (monitor, 2, winThread);
Thread thProd2 = new Thread (prod2);
Vector<Cerchio> vtProd2 = new Vector<Cerchio>();
vtProd2 = prod2.getElements();
int nCerchiRgnProd2 = vtProd2.size();
winThread.modificaRegione (2, nCerchiRgnProd2, vtProd2);
Continua …
38
THREAD
// Creazione del consumatore
Consumatore cons = new Consumatore (monitor, winThread);
Thread thCons = new Thread (cons);
// Attivazione dei thread
thProd1.start();
thProd2.start();
thCons.start();
}
Monitor
39
THREAD
Pipeline
Schema PIPELINE
Variante dello schema Produttore Consumatore
Utilizza le classi PipedReader e PipedWriter che realizzano l’ I/O fra thread
mediante pipe (canale di comunicazione)
TP
Usa PipedWriter
per scrivere su pipe
Pipe
TCons
Usa PipedReader
per leggere da pipe
PipedReader va agganciato
a PipedWriter
per realizzare la pipe
Il thread consumatore (contenente PipedReader) è bloccato
quando la pipeline è sotto controllo del produttore (contenente PipedWriter)
oppure è vuota
40
THREAD
Pipeline
Trasferire stringhe del tipo #k* (k = 1 .. 5) da un produttore ad un consumatore
41
THREAD
Pipeline
public class Trasmettitore extends Thread {
private PipedWriter out = new PipedWriter();
private char[] bufChar = new char[3];
public PipedWriter getPipedWriter() { return out; }
public void run() {
for (char c = '1'; c <= '5'; c++) {
try {
bufChar[0] = '#';
bufChar[1] = c;
out.write (bufChar, 0, 2);
bufChar[2] = '*';
out.write (bufChar, 2, 1);
sleep (100);
} catch (Exception e) { }
}
}
}
// Scrive i primi 2 caratteri sulla pipe
// Scrive il 3 carattere sulla pipe
42
THREAD
Pipeline
public class Ricevitore extends Thread {
private PipedReader in;
private char[] bufChar = new char[3];
public Ricevitore (Trasmettitore mittente) throws IOException {
in = new PipedReader (mittente.getPipedWriter());
}
public void run() {
try {
while (true) {
in.read (bufChar, 0, 3);
System.out.println ("Stringa trasferita: "
+ bufChar[0] + bufChar[1] + bufChar[2]);
}
} catch (IOException e) { }
}
}
43
THREAD
public class Pgm_xPipeLine {
public static void main (String[] args) throws Exception {
Trasmettitore mittente = new Trasmettitore();
Ricevitore destinatario = new Ricevitore (mittente);
mittente.start();
destinatario.start();
}
}
Pipeline