PROGRAMMAZIONE CONCORRENTE IN JAVA



Documenti analoghi
Monitor. Introduzione. Struttura di un TDA Monitor

Realizzazione di Politiche di Gestione delle Risorse: i Semafori Privati

Il costrutto monitor [Hoare 74]

Java Virtual Machine

Java threads (2) Programmazione Concorrente

Tipi primitivi. Ad esempio, il codice seguente dichiara una variabile di tipo intero, le assegna il valore 5 e stampa a schermo il suo contenuto:

Ottava Esercitazione. introduzione ai thread java mutua esclusione

Il costrutto monitor [Hoare 74]

Esercizi sul Monitor in Java

Sistemi Operativi. Lez. 13: primitive per la concorrenza monitor e messaggi

Esercitazioni 7 e 8. Bounded Buffer con sincronizzazione Java (1)

Pronto Esecuzione Attesa Terminazione

Soluzione dell esercizio del 2 Febbraio 2004

SAPIENZA Università di Roma Facoltà di Ingegneria dell Informazione, Informatica e Statistica

SOMMARIO Coda (queue): QUEUE. QUEUE : specifica QUEUE

Multithreading in Java. Fondamenti di Sistemi Informativi

SISTEMI OPERATIVI. Deadlock (blocco critico) Domande di verifica. Luca Orrù Centro Multimediale Montiferru 04/06/2007

Un esercizio d esame. Flavio De Paoli

12 - Introduzione alla Programmazione Orientata agli Oggetti (Object Oriented Programming OOP)

GESTIONE DEI PROCESSI

1. Che cos è la multiprogrammazione? Si può realizzare su un sistema monoprocessore? 2. Quali sono i servizi offerti dai sistemi operativi?

Sistemi Operativi Esercizi Sincronizzazione

Programmazione concorrente in Java. Dr. Paolo Casoto, Ph.D

Corso di Informatica

FONDAMENTI di INFORMATICA L. Mezzalira

Con il termine Sistema operativo si fa riferimento all insieme dei moduli software di un sistema di elaborazione dati dedicati alla sua gestione.

Terza Esercitazione. Unix - Esercizio 1. Unix System Call Exec Java Introduzione Thread

Realizzazione di una classe con un associazione

Coordinazione Distribuita

Oggetti Lezione 3. aspetti generali e definizione di classi I

Informatica 3. Informatica 3. LEZIONE 6: Il controllo dell esecuzione. Lezione 6 - Modulo 1. Errori durante l esecuzione. Il controllo dell esecuzione

DTI / ISIN / Titolo principale della presentazione. La cena dei filosofi. Amos Brocco, Ricercatore, DTI / ISIN. 14 maggio 2012

La struttura dati ad albero binario

Sistemi Operativi GESTIONE DELLA MEMORIA SECONDARIA. D. Talia - UNICAL. Sistemi Operativi 11.1

Sistemi Operativi. Memoria Secondaria GESTIONE DELLA MEMORIA SECONDARIA. Struttura del disco. Scheduling del disco. Gestione del disco

Algoritmi di Ricerca. Esempi di programmi Java

Funzioni in C. Violetta Lonati

progam ponteasensounicoalaternato ; type dir = ( nord, sud );

Progettazione : Design Pattern Creazionali

Prova di Laboratorio di Programmazione

Registratori di Cassa

Corso di Sistemi Operativi Ingegneria Elettronica e Informatica prof. Rocco Aversa. Raccolta prove scritte. Prova scritta

Corso di Laurea in Ingegneria Gestionale Esame di Informatica a.a settembre 2011

CIRCOLARE RILEVA WEB del 19/05/2009

Il descrittore di processo (PCB)

ACCESSO AL SISTEMA HELIOS...

Un sistema operativo è un insieme di programmi che consentono ad un utente di

(Esercizi Tratti da Temi d esame degli ordinamenti precedenti)

Scheduling della CPU. Sistemi multiprocessori e real time Metodi di valutazione Esempi: Solaris 2 Windows 2000 Linux

LA SINCRONIZZAZIONE TRA PROCESSI

Thread: sincronizzazione Esercitazioni del 09 Ottobre 2009

I file di dati. Unità didattica D1 1

Deadlock (stallo) Parte III. Deadlock

Inizializzazione, Assegnamento e Distruzione di Classi

Java: Compilatore e Interprete

APPUNTI DI MATEMATICA LE FRAZIONI ALGEBRICHE ALESSANDRO BOCCONI

5.2.1 RELAZIONI TRA TABELLE Creare una relazione uno-a-uno, uno-a-molti tra tabelle 9

Telematica II 17. Esercitazione/Laboratorio 6


Programmazione concorrente in Java

appunti delle lezioni Architetture client/server: applicazioni server

Introduzione. Coordinazione Distribuita. Ordinamento degli eventi. Realizzazione di. Mutua Esclusione Distribuita (DME)

Corso di Linguaggi di Programmazione

Esercizi sugli Oggetti Monitor

Esercitazioni di Progettazione del Software. Esercitazione (Prova al calcolatore del 17 settembre 2010)

Corso di Informatica

Computazione multi-processo. Condivisione, Comunicazione e Sincronizzazione dei Processi. Segnali. Processi e Threads Pt. 2

Matematica in laboratorio

Synchronized (ancora)

Mac Application Manager 1.3 (SOLO PER TIGER)

Esercitazione finale per il corso di Sistemi Operativi (A.A. 2004/2005)

LE FUNZIONI A DUE VARIABILI

Gestione Turni. Introduzione

Gestione della memoria centrale

Esercizi Capitolo 6 - Alberi binari di ricerca

Programmazione a Oggetti Lezione 10. Ereditarieta

Modulo 4: Ereditarietà, interfacce e clonazione

LABORATORIO DI PROGRAMMAZIONE EDIZIONE 1, TURNO B

COLLI. Gestione dei Colli di Spedizione. Release 5.20 Manuale Operativo

Siti web centrati sui dati Architettura MVC-2: i JavaBeans

10 - Programmare con gli Array

Linguaggi Corso M-Z - Laurea in Ingegneria Informatica A.A Esercitazione. Programmazione Object Oriented in Java

Memoria secondaria. Struttura del disco. Scheduling del disco. Gestione dell unità a disco. Affidabilità dei dischi: RAID

Introduzione ai Metodi Formali

B+Trees. Introduzione

Sistemi Operativi. Lezione 7 Comunicazione tra processi

La gestione dell input/output da tastiera La gestione dell input/output da file La gestione delle eccezioni

MANUALE ESSE3 Gestione Registro delle lezioni

Esercizio 1: trading on-line

Istituto Centrale per il Catalogo Unico delle Biblioteche Italiane. e per le Informazioni bibliografiche. Manuali utente per SBN WEB. Versione 1.

Consiglio regionale della Toscana. Regole per il corretto funzionamento della posta elettronica

Risolvere un problema significa individuare un procedimento che permetta di arrivare al risultato partendo dai dati

2.0 Gli archivi. 2.1 Inserire gli archivi. 2.2 Archivio Clienti, Fornitori, Materiali, Noleggi ed Altri Costi. Impresa Edile Guida all uso

Nuova procedura di Cassa Contanti Wingesfar: istruzioni per le farmacie Novembre 2009

Capitolo 7: Sincronizzazione

Introduzione alla programmazione in C

Il problema del produttore e del consumatore. Cooperazione tra processi

Architettura MVC-2: i JavaBeans

Tricks & Tips. [Access] Tutorial - ActiveX - Controllo Tree View. - Michele de Nittis - Versione: 1 Data Versione: venerdì 30 agosto 2002

Corso di Laurea Ingegneria Informatica Fondamenti di Informatica 2

Transcript:

PROGRAMMAZIONE CONCORRENTE IN JAVA Supporto per il monitor con condition esplicite Al fine di facilitare l uso del monitor di Hoare in Java, sono utili le seguenti classi e interfacce appartenenti al package sde.monitor. public interface Condition{ public void Wait(); public void Signal(); //Condition public interface ConditionP{ public void Wait( int priority ); public void Signal(); //ConditionP public final class Monitor{ private SemaforoBinario mutex=new SemaforoBinario(1); private SemaforoBinario urgent=new SemaforoBinario(0); private int urgentcount=0; public void Enter(){ mutex.p(); //Enter public void Exit(){ if( urgentcount > 0 ) urgent.v(); else mutex.v(); //Exit public Condition newcondition(){ return new ConditionVariable(); private class ConditionVariable implements Condition{ private SemaforoBinario xsem=new SemaforoBinario(0); private int xcount=0; public void Wait() { xcount++; if( urgentcount>0 ) urgent.v(); else mutex.v(); xsem.p(); xcount--; //Wait public void Signal() { if( xcount>0 ) { urgentcount++; xsem.v(); urgent.p(); urgentcount--; 40

//Signal //ConditionVariable public ConditionP newconditionp(){ return new ConditionVariablePriorited(); private class ConditionVariablePriorited implements ConditionP{ private SemaforoBinarioAPriorita xsem=new SemaforoBinarioAPriorita(0); private int xcount=0; public void Wait( int priority ) { xcount++; if( urgentcount>0 ) urgent.v(); else mutex.v(); xsem.p( priority ); xcount--; //Wait public void Signal() { if( xcount>0 ) { urgentcount++; xsem.v(); urgent.p(); urgentcount--; //Signal //ConditionVariablePriorited //Monitor La classe Monitor e l interfaccia Condition rendono automatica la dichiarazione di semafori e associati contatori, in tal modo semplificando al massimo le operazioni di programmazione. Una condition appartiene ad un monitor. La creazione di una condition si ottiene invocando il metodo newcondition() su un oggetto monitor. Di seguito si riassumono le convenzioni che il programmatore deve rispettare per un uso corretto dei monitor in Java. Un monitor si programma come una classe passiva che dichiara come variabili di istanza una variabile di tipo Monitor, es. mon, e tante variabili condition quante ne servono per il monitor. Ciascuna entry procedure va strutturata come segue: //procedure entry ep() public /*entry*/ void ep(){ mon.enter(); corpo di ep; mon.exit(); //ep; 41

Detta x una variabile condition, le relative operazioni wait e signal sono evocate semplicemente come x.wait() e x.signal() rispettivamente. Come si vede le formalità sono ridotte all osso ed il rischio di commettere errori, rispetto alle espansioni in linea, minimizzato. Per entry procedure funzionali, cioè che restituiscono esplicitamente un risultato, bisogna prestare attenzione che la mon.exit() va invocata prima dell istruzione return. Sul problema dei lettori-scrittori È stato già affrontato in precedenza mostrando una soluzione (basata sui semafori) che dà priorità ai lettori. Se la risorsa è libera, essa è attribuita ad uno scrittore od un lettore. Mentre un lettore è in accesso, eventuali altri lettori in arrivo guadagnano immediatamente l accesso alla risorsa. Mentre uno scrittore è in accesso, tutti gli altri processi devono aspettare. Uno scrittore in attesa è svegliato dall ultimo lettore che abbandona la risorsa o da uno scrittore che termini l accesso. Tale soluzione crea evidentemente il pericolo di starvation degli scrittori. Per comodità, si propone una classe LettoriScrittori che introduce il vincolo per un thread di chiedere l autorizzazione ad effettuare un operazione (es. iniziolettura etc.) e a segnalarne la conclusione (es. finelettura). public class LettoriScrittori { //Ammette la possibilita' di starvation degli scrittori SemaforoBinario mutex_scrittori=new SemaforoBinario( 1 ); SemaforoBinario mutex_lettori=new SemaforoBinario( 1 ); int contatore_lettori=0; public void iniziolettura(){ mutex_lettori.p(); contatore_lettori++; if( contatore_lettori == 1 ) mutex_scrittori.p(); mutex_lettori.v(); public void finelettura(){ mutex_lettori.p(); contatore_lettori--; if( contatore_lettori == 0 ) mutex_scrittori.v(); mutex_lettori.v(); public void inizioscrittura(){ mutex_scrittori.p(); public void finescrittura(){ mutex_scrittori.v(); //LettoriScrittori 42

Di seguito si presenta un altra soluzione, strutturata con monitor e condition esplicite, in grado di evitare la starvation. Essa è basata sulle seguenti assunzioni: iniziolettura() pone il lettore in wait se uno scrittore è in fase di accesso o se almeno uno scrittore è già in wait; diversamente consente l accesso al lettore e sveglia immediatamente altri lettori in attesa finelettura() abilita gli scrittori eventualmente in attesa inizioscrittura() pone il processo scrittore in wait se esistono lettori in attesa o un altro scrittore è già in fase di accesso finescrittura() sblocca la risorsa e sveglia eventuali processi lettori in attesa o, in alternativa, altri scrittori in attesa. Il monitor utilizza i seguenti dati: lettori (iniz 0), condition di attesa per i lettori scrittori (iniz 0), condition di attesa per gli scrittori lettoriinattesa (iniz 0), contatore dei lettori in wait su read-queue scrittoriinattesa (iniz 0), contatore degli scrittori in wait su write-queue scrittoreinaccesso (iniz false), booleano che indica se uno scrittore è già in fase di accesso lettoriinaccesso (iniz 0), contatore dei lettori in fase di accesso. //file LettoriScrittori.java package sde.lettoriscrittori; import sde.monitor.*; public class LettoriScrittori { private Monitor monitor=new Monitor(); private Condition lettori=monitor.newcondition(); private Condition scrittori= monitor.newcondition(); private int lettoriinattesa=0; private int scrittoriinattesa=0; private int lettoriinaccesso=0; private boolean scrittoreinaccesso=false; public void iniziolettura(){ monitor.enter(); if( scrittoreinaccesso scrittoriinattesa > 0 ){ lettoriinattesa++; lettori.wait(); lettoriinattesa--; lettoriinaccesso++; lettori.signal(); monitor.exit(); 43

public void finelettura(){ monitor.enter(); lettoriinaccesso--; if( lettoriinaccesso == 0 ) scrittori.signal(); monitor.exit(); public void inizioscrittura(){ monitor.enter(); if( lettoriinaccesso > 0 scrittoreinaccesso ){ scrittoriinattesa++; scrittori.wait(); scrittoriinattesa--; scrittoreinaccesso=true; monitor.exit(); public void finescrittura(){ monitor.enter(); scrittoreinaccesso=false; if( lettoriinattesa > 0 ) lettori.signal(); else scrittori.signal(); monitor.exit(); //LettoriScrittori 44

Sul problema dei cinque filosofi È stata a suo tempo discussa un implementazione basata su monitor che evita il deadlock. A questo proposito si ricorda che l idea è di far richiedere ad un filosofo che intenda mangiare entrambe le forchette con un azione indivisibile. Si osserva qui che tale soluzione, mentre evita il deadlock non impedisce la starvation di un filosofo. Infatti è possibile, ad esempio, che i filosofi 0 e 3 ottengano le forchette e mangino, che il filosofo 4 voglia mangiare, che il filosofo 0 deponga le forchette, che il filosofo 0 richieda nuovamente le forchette e le ottenga in quanto il filosofo 3 non ha ancora rilasciato le sue e cosi via. Di seguito si fornisce una soluzione che evita deadlock e starvation e utilizza i seguenti dati: un array fork di 5 elementi (uno per filosofo), che contiene di momento in momento il numero di forchette a disposizione di ciascun filosofo un array count di 5 elementi (uno per filosofo), che contiene il contatore delle volte che ciascun filosofo ha mangiato La starvation è evitata assegnando ad un filosofo le due forchette richieste solo nel caso che entrambi i filosofi vicini non abbiano mangiato meno volte. In altre parole, dal momento che un filosofo emette la richiesta delle forchette, esso sarà costretto ad attendere o perchè non sono disponibili entrambe le forchette, o perchè il filosofo ha mangiato sino ad ora più volte dei suoi vicini. Si presentano due versioni della soluzione organizzate in una classe Manager che controlla il comportamento dei filosofi. La prima versione utilizza direttamente i meccanismi propri di Java (wait() e notify()). La seconda, invece, si basa su monitor e condition espliciti. Le variabili locali sin e des denotano i due filosofi vicini a quello corrente who. Si presti attenzione alla costruzione di oggetti array attraverso un aggregato di inizializzazione (new è implicita in questi casi). // file Manager.java package sde.filosofi; public class Manager{ private byte fork[]={ 2, 2, 2, 2, 2 ; private int count[]={ 0, 0, 0, 0, 0 ; public synchronized void getfork( int who ) { int sin=(who+4)%5, des=(who+1)%5; //filosofi adiacenti a who while( fork[who]<2 ( (count[who]>count[sin]) (count[who]>count[des]) ) ) { if( fork[who]<2 ) System.out.println("Filosofo "+who+" attende forchette"); else System.out.println("Filosofo "+who+" attende per fairness"); 45

try { wait(); catch(interruptedexception e) { fork[sin]--; fork[des]--; ;//getfork public synchronized void putfork( byte who ) { System.out.println("Filosofo "+who+" rilascia forchette"); int sin=(who+4)%5, des=(who+1)%5; fork[sin]++; fork[des]++; count[who]++; notifyall(); ;//putfork //Manager La struttura di un generico filosofo è mostrata di seguito: public class Filosofo extends Thread { private byte io; private Manager m; public Filosofo( byte io, Manager m ){ this.io=io; this.m=m; public void run() { while(true){ System.out.println("Filosofo "+io+" pensa..."); try{ sleep( (int)(math.random()*5000 ) ); catch(interruptedexception e) {; m.getfork( io ); System.out.println("Filosofo "+io+" mangia..."); try{ sleep( (int)(math.random()*5000 ) ); catch(interruptedexception e) {; m.putfork( io ); //run //Filosofo Una possibile classe di test è la seguente: // file T5F.java import sde.filosofi.*; public class T5F{ public static void main(string args[]) { Manager m=new Manager(); Filosofo f[]=new Filosofo[5]; for(byte i=0;i<f.length;i++) { 46

f[i]=new Filosofo( i, m ); f[i].start(); //T5F Si presenta ora una differente implementazione della classe Manager fondata sui concetti di monitor e condition. Nessuna modifica è richiesta alle classi Filosofo e T5F. //file Manager.java package sde.filosofi; import sde.monitor.*; public class Manager{ private byte fork[]={ 2, 2, 2, 2, 2 ; private int count[]={ 0, 0, 0, 0, 0 ; private Monitor monitor=new Monitor(); private Condition Fil[]={ monitor.new Condition(), monitor.new Condition(), monitor.new Condition(), monitor.new Condition(), monitor.new Condition() ; public void getfork( int who ) { monitor.enter(); int sin=(who+4)%5, des=(who+1)%5; while( fork[who]<2 ((count[who]>count[sin]) (count[who]>count[des])) ) { if( fork[who]<2 ) System.out.println("Filosofo "+who+" attende forchette"); else System.out.println("Filosofo "+who+" attende per fairness"); Fil[who].Wait(); fork[sin]--; fork[des]--; monitor.exit(); ;//getfork public void putfork( byte who ) { monitor.enter(); System.out.println("Filosofo "+who+" rilascia forchette"); int sin=(who+4)%5, des=(who+1)%5; fork[sin]++; fork[des]++; count[who]++; 47

if( fork[sin]==2 ) Fil[sin].Signal(); if( fork[des]==2 ) Fil[des].Signal(); monitor.exit(); ;//putfork //Manager Nella nuova formulazione della classe Manager è stato introdotto un array Fil di condition, una condition per filosofo, ed una variabile Monitor. Allorquando un filosofo non ha entrambe le forchette o comunque deve attendere per ragioni di fairness, si pone in wait sulla sua condition. Gestione di buffer pool per grandi quantità di dati Si tratta di un estensione del problema produttore-consumatore-buffer al caso in cui ogni elemento di dato da trasferire dal produttore al consumatore è di grosse proporzioni. Per migliorare l efficienza dell applicazione, si cerca di evitare che il dato venga prima generato su un area dati locale del produttore, quindi copiato dal produttore sul buffer, quindi dal buffer ad un area dati locale del consumatore per consentirne il consumo. A questo proposito si utilizza un pool di buffer liberi. Il produttore genera un dato riempiendo un buffer libero del pool. Quindi trasmette al consumatore l indirizzo o indice del pool in cui c è il dato. Lo schema è utilizzabile tra più coppie <prod,cons> collegate ciascuna da un diverso buffer circolare, i cui elementi sono indici del pool di buffer liberi condiviso tra tutte le coppie. Q1 P1 C1 Q2 P2 C2... Qi Pi Ci Pool di buffer liberi Il comportamento di un generico produttore/consumatore è schematizzato in pseudo codice di seguito: produttore: while(true) { 48

acquisisce un buffer dal pool e annota il suo indirizzo riempie il buffer aggiunge l indirizzo del buffer nella coda condivisa col consumatore consumatore: while(true) { rimuove dalla coda condivisa col produttore l indirizzo di un buffer svuota il buffer del pool rende libero l indirizzo del buffer utilizzato Ovviamente, operazioni di sincronizzazione e mutua esclusione vanno adottate sia tra produttore e consumatore nei riguardi della coda condivisa Qi, che nei confronti del pool di buffer liberi condiviso tra tutte le coppie. Si può programmare una classe monitor Gestore di una generica Qi, con operazioni get e put. Il monitor va quindi instanziato per ogni coppia <prod,cons>. Tale classe Gestore è identica alla classe BoundedBuffer vista in precedenza. Un altro monitor, Allocatore di risorse, con operazioni Acquisisci e Rilascia, va programmato per la gestione del pool. Un produttore potrebbe essere costretto ad attendere se un buffer libero non esiste nel pool. In realtà, il monitor Allocatore potrebbe mantenere dentro di sè un array Pool di indirizzi alle risorse (buffer) fisiche esistenti all esterno del monitor. Tale array andrebbe inizializzato con gli indirizzi dei vari buffer liberi esterni. In termini Java si ha: import sde.monitor.*; public class Allocatore { int[] Pool; int size, testa; Monitor monitor=new Monitor(); Condition bufferlibero=monitor.newcondition(); public Allocator( int[] addr ){ Pool=new int[addr.length]; size=addr.length; for(int i=0; i<pool.length; i++) Pool[i]=addr[i]; testa=size-1; //Allocatore 49

public int Acquisisci() { monitor.enter(); if(testa==-1) bufferlibero.wait(); int buffloc=pool[testa ]; monitor.exit(); return buffloc; //Acquisisci public void Rilascia( int buffloc ) { monitor.enter(); Pool[++testa]=buffloc; bufferlibero.signal(); monitor.exit(); ;//Rilascia //Allocatore Come si vede, Pool è gestito semplicemente a stack. Schedulatore del movimento della testina di un disco Il disco di un sistema di calcolo è una delle unita di I/O più importanti. Dall efficienza degli accessi al disco dipendono le prestazioni dell intero sistema. In quanto segue si discute un algoritmo di scheduling del movimento della testina capace di ottimizzare le prestazioni evitando starvation delle richieste. Il tempo di accesso al disco consiste in generale di tre contributi: tempo di seek (o di posizionamento) tempo di latenza (o di rotazione) tempo di trasferimento Il tempo di posizionamento è quello richiesto per spostare il braccio mobile con la testina di lettura/scrittura sulla traccia interessata dall operazione di accesso (si ricorda che un indirizzo su disco consiste di <numero di traccia, numero di settore nella traccia>. Dopo questo tempo, occorre attendere il ritardo di latenza per far giungere il settore sotto la testina. Statisticamente questo tempo può essere assunto pari alla metà del tempo di rotazione del disco. Segue infine il tempo di trasferimento che dipende evidentemente dall ammontare dell informazione in gioco. Dei tre tempi, quello iniziale di posizionamento è fondamentale ed è oggetto di controllo con un assegnato algoritmo di scheduling dei movimenti della testina. In un caso possibile, la testina potrebbe essere spostata, rispetto alla posizione corrente, sulla traccia più vicina in base alle richieste pendenti di accesso. 50

Quest algoritmo comporterebbe il rischio di ritardare indefinitamente alcune richieste. In alternativa, si potrebbe far muovere la testina da un bordo all altro del disco, attuando le richieste pendenti corrispondenti alle varie posizioni raggiunte, quindi invertendo il senso di spostamento ed esaudendo le altre richieste (algoritmo SCAN), o ripartendo di nuovo dall estremo iniziale (algoritmo C-SCAN). Un altra possibilità sarebbe quella di far muovere la testina non da un estremo all altro sistematicamente, ma limitando gli spostamenti alle ultime tracce, in ogni senso di marcia, oggetto di richiesta (gli algoritmi sono denominati rispettivamente LOOK e C-LOOK). In quanto segue si prende in considerazione un algoritmo di scheduling tipo LOOK: si fa muovere la testina nei due sensi di moto e durante ogni senso si eseguono le richieste. Si introduce un monitor DiskManager che esporta due procedure entry richiesta(traccia-dest:int) rilascio(), da chiamare a completamento di una operazione di I/O su disco. Un processo che richieda un accesso al disco dovrebbe seguire il protocollo: controllore-disco.richiesta( numero traccia ); legge o scrive sul disco alla traccia richiesta controllore-disco.rilascio; Il monitor in ogni momento può essere occupato a servire una richiesta. In questo caso una nuova richiesta va posta in wait. Il particolare senso di marcia in corso, se in (da traccia 1 a max traccia) o out, costituisce una variabile di stato. Inoltre servono due variabili condition, diciamole motoin e motoout su cui accodare appropriatamente le richieste, con annessi contatori count-motoin e count-motoout. Cura particolare va posta sulla gestione delle richieste che sopraggiungono dinamicamente e che si riferiscono alla posizione corrente della testina. Accettando di servirle subito, si potrebbe generare starvation di altre richieste. È opportuno quindi porre in wait queste richieste in modo da poterle obbedire al prossimo cambio di senso di moto. È evidente l interesse di schedulare le richieste in modo tale da riprenderle in base alla minima vicinanza alla posizione corrente. A questo scopo si suppone di utilizzare uno schema di monitor con condition con wait a priorità. La traccia destinazione di una richiesta può essere utilizzata come priorità: più piccolo è il suo valore, maggiore è la priorità. Nel senso di moto in, è ovvio che la successione delle richieste pendenti sulla condition motoin è utile che sia per traccia crescente. Su 51

motoout, invece, è opportuno far trovare per prime le richieste di traccia più grande. Dunque come priorità è utilizzabile, in questo caso, la differenza max_traccia - traccia richiesta. Segue una formulazione della classe DiskManager. //file DiskManager.java package sde.diskmanager; import sde.monitor.*; public class DiskManager { private final int max_traccia; private final int in=0, out=1; private int direzione=in; private boolean occupato=false; private int posizione=1; private Monitor m=new Monitor(); private ConditionP motoin=m.newconditionp(); private ConditionP motoout= m.newconditionp(); private int count_motoin=0, count_motoout=0; public DiskManager( int max_traccia ){ this.max_traccia=max_traccia; public void richiesta( int numero_traccia ){ m.enter(); if( occupato ){ if( posizione < numero_traccia posizione == numero_traccia && direzione == out ){ count_motoin++; motoin.wait( numero_traccia ); count_motoin--; else { count_motoout++; motoout.wait( max_traccia - numero_traccia ); count_motoout--; else{ //fissa senso di moto if( posizione <= numero_traccia ) direzione=in; else direzione=out; occupato=true; posizione=numero_traccia; m.exit(); //richiesta public void rilascio(){ m.enter(); occupato=false; if( direzione==in) if( count_motoin==0 ){ direzione=out; motoout.signal(); 52

else motoin.signal(); else if( count_motoout==0 ){ direzione=in; motoin.signal(); else motoout.signal(); m.exit(); //rilascio //DiskManager Si nota che il metodo richiesta schedula richieste per la traccia corrente sulla condition associata con il senso di moto opposto a quello corrente. Ad ogni modo, procedendo in un certo senso di marcia si processano tutte le richieste comunque presenti per il senso di moto. Richieste per una stessa traccia sono processate in ordine di arrivo FCFS. Il metodo richiesta, se invocato mentre occupato=false, provvede a fissare il senso di moto. La classe DiskManager richiede la disponibilità di monitor con condition a priorità (ConditionP). L implementazione di questo tipo di condition (si riveda la classe Monitor fornita in precedenza) dipende dalla classe semaforo binario a priorità richiamata di seguito. //file SemaforoBinarioAPriorita.java package sde.semaforo; import java.util.*; class Tripla{ Thread id; int priority; boolean sveglia; Tripla( Thread id, int priority, boolean sveglia ){ this.id=id; this.priority=priority; this.sveglia=sveglia; public class SemaforoBinarioAPriorita{ private Vector codaapriorita=new Vector(); private int contatore; public SemaforoBinarioAPriorita( int contatore ) throws BadInitializationException { if( contatore<0 contatore>1 ) throw new BadInitializationException(); this.contatore=contatore; public synchronized void P( int priority ){ 53

if( contatore==0 ){ if( codaapriorita.size()>0 && ((Tripla)codaAPriorita.get(0)).sveglia && priority < ((Tripla)codaAPriorita.get(0)).priority ){ //preemption sotto una V in corso int i; for( i=0; i<codaapriorita.size(); i++ ) if(!((tripla)codaapriorita.get(i)).sveglia ) break; ((Tripla)codaAPriorita.get(i-1)).sveglia=false; else { //inserisci in ordine questo thread su codaapriorita int pos=0; while( pos<codaapriorita.size() && ((Tripla)codaAPriorita.get(pos)).priority<=priority ) pos++; codaapriorita.add( pos, new Tripla( Thread.currentThread(), priority, false) ); while( true ){ try{ wait(); catch( InterruptedException e ){; Tripla t=(tripla)codaapriorita.get(0); if( Thread.currentThread()==t.id && t.sveglia ) { codaapriorita.remove(0); if( codaapriorita.size()>0 && ((Tripla)codaAPriorita.get(0)).sveglia ) notifyall(); break; contatore=0; public synchronized void V(){ if( codaapriorita.size()==0 ) contatore=1; else{ int i; for( i=0; i<codaapriorita.size(); i++ ) if(!((tripla)codaapriorita.get(i)).sveglia ) break; if( i<codaapriorita.size() ) ((Tripla)codaAPriorita.get(i)).sveglia=true; notifyall(); //SemaforoBinarioAPriorita La classe SemaforoBinarioAPriorita mantiene una lista codaapriorita in cui un thread che esegua P(p) e si sospenda dà luogo ad un nodo che occupa un posto fissato dalla priorità p. Ovviamente dal punto di vista Java l ordine di attesa è 54

diverso. Al tempo di una V si sveglia il thread in testa alla coda ponendo il campo sveglia del relativo nodo a true. Tutti gli altri thread in attesa hanno il campo sveglia a false. E possibile una situazione di preemption che si verifica allorquando, al tempo di una P, il thread richiedente trova il semaforo rosso e dovrebbe entrare in attesa. Tuttavia il nuovo thread ha priorità massima ed il thread in testa a codaapriorita è stato flaggato come da risvegliare. In queste condizioni, il thread in testa alla lista viene costretto a riaddormentarsi mentre il thread a massima priorità entra in sezione critica. Gli altri dettagli dovrebbero essere auto-esplicativi. L interfaccia ConditionP è identica a Condition salvo che ora utilizza SemaforoBinarioAPriorita. Nessuna modifica è richiesta a Monitor.java. 55

Casi di studio Buffer limitato generico Si ripresenta per comodità una soluzione al problema del buffer limitato che accetta come elementi valori di tipo Object. Per generalità si propone una classe astratta BufferLimitato e si forniscono tre implementazioni rispettivamente basate sui semafori (BufferLimitatoS), il monitor di Hoare (BufferLimitatoMH) ed il monitor nativo di Java (BufferLimitatoJS). Tutte le classi sono parte del package sde.buffer. Anche il programma di prova TB.java è inserito nello stesso package. Una classe astratta e generica BufferLimitato (Java 1.5) //file BufferLimitato.java package sde.buffer; public abstract class BufferLimitato<T>{ protected int n; protected T [] buffer; protected int in=0, out=0, count=0; public BufferLimitato( int n ){ this.n=n; buffer=(t[])new Object[n]; public abstract T get(); public abstract void put( T value ); //BufferLimitato Una classe BufferLimitatoS basata sui semafori //file BufferLimitatoS.java package sde.buffer; import sde.semaforo.*; public class BufferLimitatoS<T> extends BufferLimitato<T>{ protected SemaforoBinario mutex=new SemaforoBinario(1); protected SemaforoContatore empty; protected SemaforoContatore full; public BufferLimitatoS( int n ){ super(n); empty=new SemaforoContatore(n); full=new SemaforoContatore(0); 56

public T get(){ full.p(); mutex.p(); T ans=buffer[out]; out=(out+1)%n; count--; mutex.v(); empty.v(); return ans; //get public void put( T value ) { empty.p(); mutex.p(); buffer[in]=value; in=(in+1)%n; count++; mutex.v(); full.v(); //put //BufferLimitatoS Una classe BufferLimitatoMH basata sul monitor di Hoare //file BufferLimitatoMH.java package sde.buffer; import sde.monitor.*; public class BufferLimitatoMH<T> extends BufferLimitato<T> { protected Monitor m=new Monitor(); protected Condition nonempty=m.newcondition(); protected Condition nonfull=m.newcondition(); public BufferLimitatoMH( int n ){ super(n); public T get() { m.enter(); if( count==0 ) nonempty.wait(); T ans=buffer[out]; out=(out+1)%n; count--; nonfull.signal(); m.exit(); return ans; //get public void put( T value ) { m.enter(); if( count==n ) nonfull.wait(); buffer[in]=value; in=(in+1)%n; count++; nonempty.signal(); m.exit(); //put //BufferLimitatoMH 57

Una classe BufferLimitatoJS basata sul monitor nativo di Java //file BufferLimitatoJS.java package sde.buffer; public class BufferLimitatoJS<T> extends BufferLimitato<T> { public BufferLimitatoJS( int n ){ super(n); public synchronized T get() { while( count==0 ) try { wait(); catch (InterruptedException ignored) { T ans=buffer[out]; out=(out+1)%n; count--; notifyall(); return ans; //get public synchronized void put( T value ) { while( count==n ) try { wait(); catch (InterruptedException ignored) { buffer[in]=value; in=(in+1)%n; count++; notifyall(); //put //BufferLimitatoJS Un programma di prova TB.java //file TB.java package sde.buffer; class Producer extends Thread{ int id; int seed; BufferLimitato<Integer> bl; public Producer( int id, int seed, BufferLimitato<Integer> bl ){ this.id=id; this.seed=seed; this.bl=bl; public void run(){ while( true ){ System.out.println("Producer#"+id+" produces item# "+seed); bl.put( new Integer( seed ) ); //oppure: bl.put( seed ); //autoboxing dei tipi di base seed++; try{ sleep( (int)(math.random()*500 ) ); catch( InterruptedException e ){ //Producer 58

class Consumer extends Thread{ int id; BufferLimitato<Integer> bl; public Consumer( int id, BufferLimitato<Integer> bl ){ this.id=id; this.bl=bl; public void run(){ while( true ){ int x=((integer)bl.get()).intvalue(); //oppure: int x=bl.get(); //auto-unboxing System.out.println("Consumer#"+id+" got item# "+x); //Consumer public class TB{ public static void main( String []args ){ BufferLimitato<Integer> b=new BufferLimitatoMH<Integer>( 5 ); Producer p1=new Producer( 1, 0, b ); Producer p2=new Producer( 2, 1000000, b ); Consumer c1=new Consumer( 1, b ); Consumer c2=new Consumer( 2, b ); Consumer c3=new Consumer( 3, b ); p1.start(); p2.start(); c1.start(); c2.start(); c3.start(); //TB TB.java è parte del package sde.buffer. Tutto ciò semplifica l accesso alle classi BufferLimitatoS, BufferLimitatoMH, BufferLimitatoJS. Tuttavia per l esecuzione occorre avvertire l interprete di Java che il main è in una classe dello stesso package come segue: >java sde.buffer.tb INVIO ossia specificando per esplicito il package di appartenenza di TB.class. La soluzione proposta rende possibile cambiare l implementazione del buffer modificando nel metodo main l indicazione (al lato destro di BufferLimitato b=new BufferLimitatoXX( 5 )) della classe utilizzata. Nessun altra modifica è richiesta. Barbiere Addormentato Si deve gestire un salone in cui è presente un solo barbiere, una sedia di servizio ed un certo numero di sedie per l attesa dei clienti. Quando non ci sono clienti da servire, il barbiere si addormenta sulla sedia di servizio. Un cliente che arrivi e trovi il barbiere addormentato, lo sveglia e passa immediatamente in servizio. Un cliente che trovi il barbiere in servizio e qualche posto libero, aspetta il suo turno. Se non esistono posti a sedere, il cliente va via non servito. 59

Si mostrano tre soluzioni utilizzando rispettivamente i meccanismi Java standard (SaloneJS), il monitor di Hoare (SaloneMH) e i semafori binari (SaloneSB). Le realizzazioni sono parte del package sde.salone. La classe astratta Salone definisce le entità comuni alle varie implementazioni. Ogni estensione di Salone deve implementare il metodo run() dell interfaccia Runnable dal momento che il thread barbiere è incorporato all interno del salone. L operazione richiesta() è l unica che può essere utilizzata dai clienti. Essa ritorna false se al momento del tentativo il salone è tutto occupato, true se il cliente va via servito. //Salone.java package sde.salone; public abstract class Salone implements Runnable{ protected int sedie, posti; protected Thread barbiere=null; protected boolean dorme, //true se il barbiere dorme cliente; //true se un cliente e' seduto per servizio public Salone( int sedie ){ this.sedie=sedie; posti=sedie; dorme=true; cliente=false; barbiere=new Thread(this); public abstract boolean richiesta(); public abstract void run(); //Salone Una prima estensione di Salone si basa sull uso dei metodi synchronized e delle operazioni wait(), notify(), notifyall() di Java. //SaloneJS.java package sde.salone; public class SaloneJS extends Salone{ protected boolean fineattesa=false; public SaloneJS( int sedie ){ super( sedie ); barbiere.start(); 60

public synchronized boolean richiesta(){ if( posti==0 ) return false; if( posti<sedie!dorme ){ posti--; while( true ){ try{ wait(); catch(interruptedexception e){ if( fineattesa ){ fineattesa=false; cliente=true; //cliente si accomoda su poltrona barbiere notifyall(); break; else{ dorme=false; cliente=true; notify(); //cliente aspetta fine servizio while( cliente ) try{ wait(); catch(interruptedexception e){ return true; public void run(){ while( true ){ synchronized(this){ if(!cliente ){ if( posti==sedie ){ dorme=true; //barbiere dorme per assenza di clienti while( dorme ) try{ wait(); catch(interruptedexception e){ else{ posti++; fineattesa=true; notify(); //barbiere attende cliente per servizio while(!cliente ) try{ wait(); catch(interruptedexception e){ //servizio try{ Thread.sleep( (int)(math.random()*1000) ); catch(interruptedexception e){ synchronized(this){ cliente=false; //cliente si alza per fine servizio notifyall(); //while //run //SaloneJS 61