Strumenti Utente

Strumenti Sito


lpr-b:lpr-b-09:esercizi

Questa è una vecchia versione del documento!


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.

Soluzione: PrintTask.java

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.

Soluzione: SleepTask.java

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:

  1. il thread è stato interrotto, oppure
  2. 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.

Soluzione: PITask2.java

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.

Si suggerisce di definire una classe per ogni tipo di task di calcolo, fattorizzando le funzionalità comuni (come la stampa finale) in una superclasse astratta comune.

Soluzione: AsynchronousCalculator.java, Task.java, PITask.java, FibTask.java, FactTask.java

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.

Soluzione: ListInterface

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.

Il protocollo UDP

Inviare gli esercizi svolti a [email protected] con Subject “[LPR-B] Esercitazione 5”

Avviso: Per eseguire gli esercizi su di un unico host
  1. Attivare il client/sender ed il server/receiver in due diverse shell
  2. Se l’host è connesso in rete: utilizzare come indirizzo IP del mittente/destinatario l’indirizzo dell’host su cui sono in esecuzione i due processi (reperibile con InetAddress.getLocalHost( ))‏
  3. Se l’host non è connesso in rete utilizzare l’indirizzo di loopback (“localhost” o 127.0.0.1)
  4. Tenere presente che mittente e destinatario sono in esecuzione sulla stessa macchina, quindi devono utilizzare porte diverse
  5. Mandare in esecuzione per primo il server/receiver, poi il client/sender
Avviso: Per eseguire gli esercizi su più host distinti
  1. Se siete nel Laboratorio M, potete usare come host remoti gli altri computer del laboratorio: i nomi sono fujim2, fujim3, …, fujim40. Per controllare se un host è sotto Linux e raggiungibile, eseguire da shell il comando ssh <nomeHost> e fornire la password: questo aprirà una shell sul computer <nomeHost>, con directory corrente la vostra home-directory.
  2. Usando questa shell potete mandare in esecuzione su quell'host uno dei processi dell'esercizio. Se il processo non deve interagire con l'utente, lo potete eseguire in background (& dopo il comando) e usare la shell per far partire altri processi.

Esercizio 1: Invio di Datagram UDP

Scrivere un'applicazione composta da un processo Sender e un processo Receiver. Il Receiver riceve da linea di comando la porta su cui deve porsi in attesa. Il Sender riceve da linea di comando una stringa e l’indirizzo del Receiver (indirizzo IP + porta), e invia al Receiver la stringa. Il Receiver riceve la stringa e stampa, nell'ordine, la stringa ricevuta, l'indirizzo IP e la porta del mittente.

Considerare poi i seguenti punti:

  • Cosa cambia se mando in esecuzione prima il Sender, poi il Receiver rispetto al caso in cui mando in esecuzione prima il Receiver?
  • Nel processo Receiver, aggiungere un time-out sulla receive, in modo che la receive non si bocchi per più di 5 secondi. Cosa accade se attivo il receiver, ma non il sender?
  1. Modificare il codice del Sender in modo che usi lo stesso socket per inviare lo stesso messaggio a due diversi receivers. Mandare in esecuzione prima i due Receivers, poi il Sender. Controllare l'output dei Receiver.
  2. Modificare il codice del Sender in modo che esso usi due sockets diversi per inviare lo stesso messaggio a due diversi receivers. Mandare in esecuzione prima i due Receivers, poi il Sender.
  3. Modificare il codice ottenuto al passo precedente in modo che il Sender invii una sequenza di messaggi ai due Receivers. Ogni messaggio contiene il valore della sua posizione nella sequenza. Il Sender si sospende per 3 secondi tra un invio ed il successivo. Ogni receiver deve essere modificato in modo che esso esegua la receive in un ciclo infinito.
  4. Modificare il codice ottenuto al passo precedente in modo che il Sender non si sospenda tra un invio e l’altro. Cosa accade?
  5. Modificare il codice iniziale in modo che il Receiver invii al Sender un ack quando riceve il messaggio. Il Sender visualizza l’ack ricevuto.

Esercizio 2: CountDown Server

Si richiede di programmare un server CountDownServer che fornisce un semplice servizio: ricevuto da un client un valore intero n, il server spedisce al client i valori n-1,n-2,…,1,0 in sequenza.

Il client deve calcolare il numero di pacchetti persi e quello di quelli ricevuti fuori ordine e lo visualizza alla fine della sessione. Utilizzare le classi ByteArrayOutput/InputStream per la generazione/ricezione dei pacchetti.

La interazione tra i clients e CountDownServer è di tipo connectionless. Si richiede di implementare due versioni di CountDownServer

  1. realizzare CountDownServer come un server iterativo. L’applicazione riceve la richiesta di un client, gli fornisce il servizio e solo quando ha terminato va a servire altre richieste
  2. realizzare CountDownServer come un server concorrente. Si deve definire un thread che ascolta le richieste dei clients dalla porta UDP a cui è associato il servizio ed attiva un thread diverso per ogni richiesta ricevuta. Ogni thread si occupa di servire un client.

Per testare il funzionamento del client, può essere utile usare la classe UnreliableDatagramSocket, che offre le stesse funzionalità di DatagramSocket, ma perde il pacchetto da inviare con probabilità “threshold”, una quantità compresa tra 0 e 1, modificabile con il metodo setThreshold(double) e con valore default 0.1.

lpr-b/lpr-b-09/esercizi.1256662670.txt.gz · Ultima modifica: 27/10/2009 alle 16:57 (16 anni fa) da Andrea Corradini

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki