In questa quarta e ultima parte del tutorial approfondiamo il modello produttore – consumatore già visto nella parte terza. Come ho già mostrato, il monitor agisce come un direttore d’orchestra, in cui l’orchestra è composta dai thread. Il monitor in particolare gestisce un buffer, o una qualsiasi altra struttura dati, a cui i thread devono accedere in lettura o scrittura. Nell’esempio presentato il buffer non aveva dimensione definita, quindi i thread potevano sempre inserirvi nuovi elementi. Estendiamo l’esempio imponendo che il buffer abbia una dimensione massima. In questo caso è necessario coordinare i produttori in modo che "smettano" di produrre elementi, quando il buffer è pieno.
La soluzione finale
Per evitare l’overflow del buffer sfruttiamo ancora una volta il meccanismo di wait() e notify() per addormentare i produttori quando il buffer è pieno, e svegliarli appena si libera almeno un posto. L’unico metodo da modificare è il metodo accodaRichiesta() del monitor. Esso dovrà controllare la dimensione del buffer. Se questa è uguale alla massima dimensione possibile allora deve addormentare il produttore che ha chiamato il metodo per accodare la richiesta. Ecco il codice:
import java.util.Vector;
public class Monitor {
// Coda delle richieste private
Vector<String> codaRichieste = new Vector<String>();
// Dimensione max della coda
private int max = 0;
/*
* Crea il monitor impostando la
* dimensione massima del bufffer
* (coda) gestito.
*/
public Monitor(int max){
this.max = max;
}
// Preleva la prima richiesta in coda
public synchronized String prelevaRichiesta(){
while (codaRichieste.size() == 0){
try {
wait();
}
catch (InterruptedException e){
e.printStackTrace();
}
}
String element = codaRichieste.remove(0);
notifyAll();
return element;
}
// Accoda una nuova richiesta
public synchronized void accodaRichiesta(String richiesta){
System.out.println("Elementi buffer: " + codaRichieste.size());
while (codaRichieste.size() == max){
try {
wait();
}
catch (InterruptedException e){
e.printStackTrace();
}
}
codaRichieste.addElement(richiesta);
notifyAll();
}
}
Alcuni commenti. Si nota subito come il comportamento del metodo accodaRichiesta() sia ora quasi identico al metodo prelevaRichiesta(), a differenza che al posto di controllare se il buffer è vuoto, controlla se è pieno. Se è pieno addormenta il produttore che ha chiamato il metodo. Se invece non è pieno inserisce la richiesta e utilizza notifyAll() per svegliare gli eventuali thread consumatori in attesa di un elemento da prelevare.
Anche prelevaRichiesta() è stato modificato inserendo un notifyAll(), necessario per svegliare eventuali produttori in attesa che si liberi un posto nel buffer. Infine è stato aggiunto un attributo max, inizializzato dal costruttore, che rappresenta la dimensione massima del buffer.
Tutte le altre classi rimangono invariate. Bisogna solo ricordarsi, nel main(), di passare al costruttore del monitor la dimensione del buffer.
Provate ad avviare l’esempio: noterete che la dimensione del buffer pian piano cresce fino ad arrivare al valore impostato in max. Dopodichè i thread continuano ad addormentarsi e svegliarsi in base ai posti disponibili nel buffer. Se l’effetto non è immediatamente evidente cambiate i tempi di attesa impostati con sleep(): in particolare allungate i tempi del consumatore. In questo modo il buffer ha tempo di riempirsi prima che intervenga un consumatore a svuotarlo.
Un’applicazione reale
Ovviamente questo modello è diffusissimo. Nel caso del webserver, ovviamente esiste un limite massimo di richieste contemporanee gestibili. Ciò è necessario per non saturare il server. i può quindi pensare che le richieste vengano inserite in un buffer e gestite secondo questo modello. Il webserver molto probabilmente avrà un solo thread produttore, in quanto la porta di ascolto (80) è unica. Questo thread andrà a inserire le richieste nel buffer, e se questo è pieno verrà addormentato. Se il buffer si satura e rimane saturo per un certo periodo di tempo, verrà ritornato all’utente un messaggio di timeout, che indica la saturazione del webserver. E’ il classico messaggio "Spiacenti, troppe connessioni simultanee, riprova più tardi".