La verifica consiste nell’implementazione di un monitor Pedaggio per la sincronizzazione di un certo numero di thread Auto e Casello. I thread Auto rappresentano delle automobili in uscita dall’autostrada e i thread Casello rappresentano dei dispositivi di pagamento automatico presso i quali pagare il pedaggio autostradale.
Un casello è identificato da un id intero compreso tra 0 e NTOLLS-1 (dove NTOLLS è il numero totale di caselli presenti nell’autostrada). Ciascun thread Casello si comporta secondo il seguente schema, dove p è l’istanza del monitor Pedaggio e FEE è una costante che rappresenta la somma da pagare al casello:
Thread Casello(id) { while(true) { // attende che tutta la quota sia pagata e poi dà il via libera p.incassa(id, FEE); } }
Un auto si comporta come segue:
Thread Auto { scegli casualmente il casello da raggiungere (con id idCasello) while( non hai raggiunto il casello ) { p.move(x, y, newx, newy); // si muove da x,y a newx,newy } int pagato = 0; while (pagato < FEE) { scegli la moneta con cui pagare (numero intero) p.paga(idCasello, moneta); pagato += moneta; } p.attendiOK(idCasello); // attende il via libera while( non sei uscito dall'autostrada ) { p.move(x, y, newx, newy); // si muove da x,y a newx,newy } p.libera(x, y); }
I metodi del monitor da implementare sono i seguenti:
// si muove dalla posizione x,y a nx,ny. Se nx,ny è occupato attende altrimenti // occupa nx,ny e libera x,y. void move(int x, int y, int nx, int ny) {} // libera la posizione x,y sulla strada (invocata quando l'auto esce definitivamente) void libera(int x, int y) {} // paga a idCasello un valore parziale che si accumula ai pagamenti precedenti: // corrisponde a inserire una moneta o banconota nella macchinetta. // Viene invocato tante volte, fino a raggiungimento della cifra corretta. void paga(int idCasello, int parziale) {} // Il casello 'idCasello' attende che il pagamento del totale sia avvenuto. Accumula i vari // pagamenti parziali e esce solamente quando il totale è stato raggiunto. // Viene invocato una volta sola e esce solo quando il totale è stato raggiunto. // Quando il pagamento è effettuato da' il via libera all'auto. void incassa(int idCasello, int totale) {} // Attende l'OK dell'avenuto pagamento prima di superare il casello. // Quando ha il via libera procede. void attendiOK(int idCasello) {}
Scaricate il codice da qui e modificate il file Pedaggio.java. Per compilare ed eseguire il programma, usate i seguenti comandi:
> javac Casello.java > java Casello
Soluzione
Una possibile soluzione commentata della verifica è la seguente:
/* * La verifica consiste nell'implementazione di un monitor Pedaggio che deve * occuparsi di: * - sincronizzare il movimento delle auto in maniera tale che non si * verifichino incidenti * - sincronizzare caselli e automobili in maniera tale che un auto non * possa superare un casello fino a quando non ha pagato per intero il * pedaggio e la sbarra del casello non è stata sollevata in modo da * permettere il passaggio dell'auto. * * Per implementare questo schema utilizziamo le seguenti variabili: * - 'occupato': matrice di booleani di dimensione pari alla dimensione * dello schermo. Viene usata per tenere traccia della posizione delle * automobili nella strada. La variabile 'occupato[x][y]' è true se la * posizione (x, y) è occupata da un'automobile, false altrimenti. * - 'pedaggi': vettore di interi di dimensione pari al numero di caselli * presenti nell'autostrada. Viene usato per tenere traccia dei pagamenti * parziali effettuati dalle automobili ai caselli. * - 'viaLibera': vettore di booleani di dimensione pari al numero di caselli * presenti nell'autostrada. Viene usato per gestire l'attesa del via * libera da parte del casello per il passaggio delle auto. La variabile * 'viaLibera[id]' è true se la sbarra del casello id è alzata (quindi * l'auto in attesa presso quel casello può passare), false altrimenti. */ public class Pedaggio { private boolean occupato[][]; private int pedaggi[]; private boolean viaLibera[]; public Pedaggio(int x, int y, int ntolls) { /* NB: in mancanza di inizializzazione esplicita, Java inizializza di * default le variabili booleane a true e quelle intere a 0. */ /* Tutte le posizioni della strada sono inizialmente libere, quindi * la matrice è inizializzata a false. */ occupato = new boolean[x][y]; /* Inizialmente non è stato effettuato alcun pagamento, quindi tutti * i pedaggi parziali sono pari a 0. */ pedaggi = new int[ntolls]; /* Nessun pagamento è stato effettuato, pertanto tutte le sbarre dei * caselli sono abbassate. */ viaLibera = new boolean[ntolls]; } public synchronized void move(int x, int y, int nx, int ny) throws InterruptedException { /* Attendi se la posizione in cui ci si vuole spostare è occupata. */ while (occupato[nx][ny]) { wait(); } /* La posizione (nx, ny) ora è occupata. */ occupato[nx][ny] = true; /* Libera la posizione in cui si trovava l'auto in precedenza. */ libera(x, y); } public synchronized void libera(int x, int y) { /* Segnala che la posizione (x, y) è libera. */ occupato[x][y] = false; /* Notifica agli altri thread che la posizione (x, y) è libera. * In questo modo, eventuali auto in attesa di questa posizione * possono provare ad occuparla. */ notifyAll(); } public synchronized void paga(int idCasello, int parziale) { /* Incrementa il pedaggio pagato finora. Per efficienza, non facciamo * alcuna 'notifyAll' in questo punto dal momento che non siamo * sicuri che l'intero pedaggio sia stato pagato, bensì lo facciamo * all'inizio della funzione 'attendiOK'. */ pedaggi[idCasello] += parziale; } public synchronized void incassa(int idCasello, int totale) throws InterruptedException { /* Attendi che il pedaggio sia completamente pagato. */ while (pedaggi[idCasello] < totale) { wait(); } /* Azzera il pedaggio per poter processare correttamente la prossima * macchina in coda. */ pedaggi[idCasello] = 0; /* Alza la sbarra per permettere il passaggio alla macchina * attualmente al casello. */ viaLibera[idCasello] = true; /* Notifica all'automobile che la sbarra si è alzata e che dunque * può uscire dal casello. */ notifyAll(); } public synchronized void attendiOK(int idCasello) throws InterruptedException { /* Notifica al casello che l'intero pedaggio è stato pagato (vedi * commento nel metodo 'paga'). */ notifyAll(); /* Attendi che la sbarra del casello sia sollevata. */ while (!viaLibera[idCasello]) { wait(); } /* La sbarra si chiude immediatamente. In questo modo siamo sicuri * di permettere il passaggio ad una macchina alla volta. */ viaLibera[idCasello] = false; } }