Questa è una vecchia versione del documento!
Indice
LPR-B-09: Esercizi assegnati a lezione
Test di ingresso
Inviare gli esercizi svolti a [email protected] con Subject “[LPR-B] Esercitazione 1”
- Si realizzi un programma Java che permette di visualizzare (sullo standard output) uno o più file di testo, mostrandone 20 righe alla volta (si pensi al comando Unix
less
). I nomi dei file da mostrare sono passati come argomenti della linea di comando, ad esempio:
java Less pippo.txt /usr/share/dict/words
Il programma deve leggere qualcosa in input prima di mostrare le prossime 20 righe. Se un file non esiste, deve segnalarlo all'utente, chiedendo se interrompere l'esecuzione o proseguire con i prossimi file.
- Si consideri il programma Java Leggimi.
- Spiegare in un commento di poche righe cosa fa il programma, indicando quali classi e quali metodi significativi usa.
- Scrivere un programma Java che prende come argomento da linea di comando il nome di una classe, e ne stampa tutte le variabili, indicando per ognuna di esse se è una variabile di classe o di istanza, e se è dichiarata
final
oppure no.
Tasks e Threads
Inviare gli esercizi svolti a [email protected] con Subject “[LPR-B] Esercitazione 2”
Esercizio 1
Scrivere un programma JAVA che attivi K thread, chiamati “T1”, “T2”, …, “TK”. Tutti i thread sono caratterizzati dallo stesso comportamento: ogni thread stampa i primi N numeri naturali, senza andare a capo (K e N sono dati in input dall'utente). Accanto ad ogni numero deve essere visualizzato il nome del thread che lo ha stampato, ad esempio usando il formato “: n [Tk] :”. Tra la stampa di un numero e quella del numero successivo ogni thread deve sospendersi per un intervallo di tempo la cui durata è scelta in modo casuale tra 0 e 1000 millisecondi.
Sviluppare due diverse versioni del programma che utilizzino le due tecniche per l'attivazione di threads presentate in questa lezione.
Esercizio 2: Interrompere un thread
Scrivere un programma che avvia un thread che va in sleep per 10 secondi. Il programma principale interrompe il thread dopo 5 secondi. Il thread deve catturare l'eccezione e stampare il tempo (in millisecondi) trascorso in sleep.
Per ottenere l'ora corrente usare il metodo System.currentTimeMillis(), consultandone la documentazione on line.
Esercizio 3: Calcolo di PiGreco
Scrivere un programma che attiva un thread T che effettua il calcolo approssimato di pigreco. Il programma principale riceve in input da linea di comando due argomenti:
- un parametro che indica il grado di accuratezza (accuracy) per il calcolo di pigreco;
- il tempo massimo di attesa dopo cui il programma principale interrompe il thread T.
Il thread T effettua un ciclo infinito per il calcolo di pigreco usando la serie di Gregory-Leibniz (pigreco = 4/1 – 4/3 + 4/5 - 4/7 + 4/9 - 4/11 …).
Il thread esce dal ciclo quando una delle due condizioni seguenti risulta verificata:
- il thread è stato interrotto, oppure
- la differenza in valore assoluto tra il valore stimato di pigreco ed il valore Math.PI (della libreria Java) è minore di accuracy.
Prima della terminazione il thread stampa il valore approssimato di pigreco calcolato fino a quel momento.
Esercizio 4
Modificare il programma dell'esercizio precedente in modo che il valore approssimato di pigreco calcolato dal thread venga stampato dal main.
Thread Pool
Inviare gli esercizi svolti a [email protected] con Subject “[LPR-B] Esercitazione 3”
Esercizio 1: Calcolatore Asincrono
Si realizzi un calcolatore asincrono, in grado cioè di calcolare espressioni senza dover attendere la fine di un calcolo prima di accettare l'espressione successiva.
Il programma deve accettare in input un'espressione in una delle seguenti forme:
- PI:precisione – calcola il valore di PI greco, come abbiamo visto in precedenza, fino all'approssimazione di 1/10precisione
- FIB:n – calcola il valore dell'n-esimo numero di Fibonacci, col metodo ricorsivo (con due chiamate)
- FACT:n – calcola il fattoriale di n, col metodo ricorsivo (con una chiamata)
- QUIT – termina il programma (una volta finiti i calcoli in sospeso)
Ad ogni espressione, il programma aggiunge un task a un threadpool, che effettuerà il calcolo e stamperà il risultato a video, e torna immediatamente a chiedere il prossimo input
I task di calcolo devono stampare, a fine esecuzione: il nome del loro thread, l'istante di creazione, l'istante di inizio esecuzione, l'istante di completamento, e il risultato del calcolo.
Esercizio 2: Raffinamenti
Con riferimento all'esercizio precedente:
- Si faccia in modo che il ciclo principale del calcolatore stampi un prompt per indicare la disponibilità a ricevere input
- Per esempio:
“Inserisci espressione: “
- Quando un calcolo termina, verrà stampato il risultato su video. Fare in modo che la stampa non confonda il prompt
- In particolare, occorre che il ciclo principale ristampi il prompt se è stato stampato un risultato
Si sperimenti poi con diverse politiche di pooling
- In particolare: qual'è il numero di thread eseguibili contemporaneamente su queste macchine, in maniera tale da minimizzare i tempi d'attesa?
- Tempo fra l'invio di una richiesta e il suo completamento
- Tempo fra l'inizio del servizio di una richiesta e il suo completamento
Esercizio 3: Un Executor Custom
- Si usi una struttura dati “coda ordinata” per implementare un Executor che scheduli i task in base a un campo priorità, indicato dal task stesso.
- La priorità (che viene stabilita in fase di creazione e/o sottomissione del task) deve anche essere usata come priorità del thread relativo.
- Si tenga presente che i thread possono essere riutilizzati per task a priorità diversa (la priorità va quindi stabilita al momento della “presa in carico” di un task).
- Si crei infine un main di prova che sottometta vari task al vostro Executor, e ne visualizzi l'ordine di sottomissione, inizio e fine esecuzione (come negli esempi visti a lezione)
Indirizzi IP e Callable
Inviare gli esercizi svolti a [email protected] con Subject “[LPR-B] Esercitazione 4”
Esercizio 1
Scrivere un programma che enumeri e stampi a video tutte le interfacce di rete del computer, usando i metodi della classe java.net.NetworkInterface.
- Usare il metodo statico getNetworkInterfaces() per ottenere una Enumeration di NetworkInterface.
- Per ogni NetworkInterface, stampare gli indirizzi IP associati ad essa (IPv4 e IPv6) e il nome dell’interfaccia.
Esercizio 2
Scrivere un programma Java Resolve che traduca una sequenza di nomi simbolici di host nei corrispondenti indirizzi IP. Resolve legge i nomi simbolici da un file, il cui nome è passato da linea di comando oppure richiesto all'utente.
- Si deve definire un task che estenda l’interfaccia Callable, e che, ricevuto come parametro un nome simbolico, provvede a tradurre il nome ritornando un InetAddress.
- Per ottimizzare la ricerca, si deve attivare un pool di thread che esegua i task in modo concorrente. Ogni volta che si sottomette al pool di thread un task, si ottiene un oggetto Future<InetAddress>, che deve essere aggiunto ad un ArrayList.
- Infine, si scorre l’ArrayList, stampando a video gli InetAddress.
Esercizio 3
Scrivere un programma che ricerca una parola chiave (key) nei file contenuti in una directory (fornita dall'utente) e nelle sue sottodirectory. Per ogni file che contiene key, si deve visualizzare il nome dei file e il contenuto della prima riga trovata che contiene key.
- Creare una classe FindKeyword che implementa Callable, alla quale si può passare una directory come parametro del costruttore, e che ritorna un array di stringhe del formato
<nome file> : <contenuto riga che contiene key>
- Il metodo search della classe FindKeyword implementa la ricerca di key all’interno di un singolo file, e ritorna una stringa formattata come sopra oppure null se key non compare.
- Creare un pool di thread a cui vengono sottomessi un task FindKeyword per ogni directory/sottodirectory, e usare gli oggetti Future restituiti per stampare a video i risultati ottenuti.
Sincronizzazione di Thread
Inviare gli esercizi svolti a [email protected] con Subject “[LPR-B] Esercitazione 4”
Esercizio 1
Si scriva un programma Java che dimostri che si possono verificare delle race conditions anche con una singola istruzione di incremento di una variabile.
- Scrivere una classe Counter che offre un metodo next() che incrementa una variabile intera locale, e un metodo getCount() che ne restituisce il valore.
- Scrivere un task TaskCounter che implementa Callable e che riceve nel costruttore un Counter e un intero n. Il task invoca la next() del Counter un numero casuale di volte compreso tra n/2 e n, e restituisce il numero casuale calcolato.
- Il main crea un Counter e un pool di threads, in cui esegue M copie di TaskCounter passando a ognuna di esse il Counter e un valore N; quindi stampa la somma dei valori restituiti dagli M threads, e il valore finale del contatore ottenuto con getCount(): se questi due valori sono diversi c'è stata una race condition. M e N devono essere forniti dall'utente.
Esercizio 2
Si consideri il metodo next() della classe Counter dell'Esercizio 1. Modificarlo in modo da renderne l'esecuzione non interrompibile, e rieseguire il programma controllando che non si verifichino più race conditions. Fare questo nei tre modi visti:
- usando un comando synchronized
- usando un lock esplicito
- dichiarando synchronized il metodo next()
Esercizio 3
- La classe Buffer presentata a lezione ha una politica Last In First Out (LIFO), quindi non preserva l'ordine. Scrivere la classe CircularBuffer che estende Buffer e realizza una politica FIFO, gestendo l'array in modo circolare.
- Definire le interfacce generiche Producer<E>, Consumer<E> e Buffer<E>, che definiscono un sistema produttore/consumatore per un generico tipo di dati E.
- Implementare le interfacce in modo che il produttore produca una sequenza di stringhe, leggendole da un file passato come parametro al task, e il consumatore scriva le stringhe che prende dal buffer in un altro file.
- Nel main, creare e attivare un produttore e due o più consumatori. Verificare che la concatenazione dei file generati dai consumatori sia uguale, a meno dell'ordine delle righe, al file letto dal produttore.
Esercizio 4
Il laboratorio di Informatica del Polo Marzotto è utilizzato da tre tipi di utenti, studenti, tesisti e professori ed ogni utente deve fare una richiesta al tutor per accedere al laboratorio. I computer del laboratorio sono numerati da 1 a 20. Le richieste di accesso sono diverse a seconda del tipo dell'utente:
- i professori accedono in modo esclusivo a tutto il laboratorio, poichè hanno necessità di utilizzare tutti i computers per effettuare prove in rete;
- i tesisti richiedono l'uso esclusivo di un solo computer, identificato dall'indice i, poichè su quel computer è istallato un particolare software necessario per lo sviluppo della tesi.
- gli studenti richiedono l'uso esclusivo di un qualsiasi computer.
I professori hanno priorità su tutti nell'accesso al laboratorio, i tesisti hanno priorità sugli studenti.
Scrivere un programma JAVA che simuli il comportamento degli utenti e del tutor. Il programma riceve in ingresso il numero di studenti, tesisti e professori che utilizzano il laboratorio ed attiva un thread per ogni utente. Ogni utente accede k volte al laboratorio, con k generato casualmente.
Simulare l'intervallo di tempo che intercorre tra un accesso ed il successivo e l'intervallo di permanenza in laboratorio mediante il metodo sleep. Il tutor deve coordinare gli accessi al laboratorio. Il programma deve terminare quando tutti gli utenti hanno completato i loro accessi al laboratorio.