Esercizi
- Home
- Esercizi del capitolo 12
Capitolo 12
Gestione dei thread
Abbiamo visto nel capitolo 12 come la gestione dei thread sia un argomento molto complesso. Tuttavia
gli esercizi che sono presentati di seguito dovrebbero risultare alquanto fattibili.
Di seguito trovate gli esercizi del capitolo.
Per ogni esercizio, cliccando sulla traccia potete vedere la relativa soluzione.
Gli esercizi caratterizzati dall'icona sono
considerati i più complessi relativamente agli argomenti trattati.
Se preferite lavorare offline, è possibile scaricare tutti gli esercizi e le relative
soluzioni in formato PDF nella sezione download.
-
Esercizio 12.a) Creazione di Thread, Vero o Falso:
1. Un thread è un oggetto istanziato dalla classe
Thread
o dalla classeRunnable
.
2. Il multithreading è solitamente una caratteristica dei sistemi operativi e non dei linguaggi di programmazione.
3. In ogni applicazione al runtime esiste almeno un thread in esecuzione.
4. A parte il thread principale, un thread ha bisogno di eseguire codice all'interno di un oggetto la cui classe implementaRunnable
o estendeThread
.
5. Il metodorun
deve essere chiamato dal programmatore per attivare un thread.
6. Il thread corrente non si identifica solitamente con il referencethis
.
7. Chiamando il metodostart
su di un thread, questo viene immediatamente eseguito.
8. Il metodosleep
è statico e permette di far dormire per un numero specificato di millisecondi il thread che legge tale istruzione.
9. Assegnare le priorità ai thread è una attività che può produrre risultati diversi su piattaforme diverse.
10. Lo scheduler della JVM non dipende dalla piattaforma su cui viene eseguito.
Soluzione
1. Falso,
Runnable
è un'interfaccia.
2. Vero.
3. Vero, il cosiddetto thread "main".
4. Vero.
5. Falso, il programmatore può invocare il metodostart
e lo scheduler invocherà il metodorun
.
6. Vero.
7. Falso.
8. Vero.
9. Vero.
10. Falso. -
Esercizio 12.b) Gestione del multi-threading, Vero o Falso:
1. Un thread astrae un processore virtuale che esegue codice su determinati dati.
2. La parola chiavesynchronized
può essere utilizzata sia come modificatore di un metodo sia come modificatore di una variabile.
3. Il monitor di un oggetto può essere identificato con la parte sincronizzata dell'oggetto stesso.
4. Affinché due thread che eseguono lo stesso codice e condividono gli stessi dati non abbiano problemi di concorrenza, è necessario sincronizzare il codice comune.
5. Si dice che un thread ha il lock di un oggetto se entra nel suo monitor.
6. I metodiwait
,notify
enotifyAll
rappresentano il principale strumento per far comunicare più thread.
7. I metodisuspend
eresume
sono attualmente deprecati.
8. Il metodonotityAll
, invocato su di un certo oggettoo1
, risveglia dallo stato di pausa tutti i thread che hanno invocatowait
sullo stesso oggetto. Tra questi verrà eseguito quello che era stato fatto partire per primo con il metodostart
.
9. Il deadlock è una condizione di errore bloccante generata da due thread che stanno in reciproca dipendenza in due oggetti sincronizzati.
10. Se un threadt1
esegue il metodorun
nell'oggettoo1
della classeC1
, e un threadt2
esegue il metodorun
nell'oggettoo2
della stessa classeC1
, la parola chiavesynchronized
non serve a niente.
Soluzione
1. Vero.
2. Falso.
3. Vero.
4. Falso.
5. Vero.
6. Vero.
7. Vero.
8. Falso, il primo thread che partirà sarà quello a priorità più alta.
9. Vero.
10. Vero. -
Esercizio 12.c)
Simulare con del codice funzionante la seguente situazione con quanto appreso in questo capitolo. Un gruppo di 10 persone è all'osservatorio astronomico per ammirare il passaggio di una cometa sfruttando il potente telescopio messo a disposizione dalla struttura. Solo una persona alla volta può usare il telescopio, e i responsabili concedono solo tre minuti a persona per completare l'osservazione. Non esiste una fila, i partecipanti accederanno al telescopio in maniera casuale. Ogni partecipante quindi attraverserà degli stati:
• lo stato "In attesa" quando starà aspettando il suo turno. Dura un tempo indefinito, che dipende da quando inizierà il turno del partecipante;Gli stati possono essere caratterizzati da delle stampe significative.
• lo stato "Osservazione" quando starà osservando la cometa tramite il telescopio. Dura esattamente 3 minuti (ma è possibile abbreviare questo tempo per eseguire l'esercizio);
• lo stato "Finito" quando il turno è finito.
Soluzione
Per prima cosa astraiamo il concetto di stato di cui si è parlato nel quesito 12.c, tramite un'enumerazione. Creiamo anche il messaggio da stampare per ogni stato:
package com.claudiodesio.osservatorio.dati; public enum Stato { IN_ATTESA("\"Sono in attesa...\""), OSSERVAZIONE("\"Tocca a me... che meraviglia!\""), FINITO("\"Ho finito.\""); private String messaggio; private Stato(String messaggio) { this.messaggio = messaggio; } public String getMessaggio() { return messaggio; } }
Poi creiamo una semplice classePartecipante
, che ha tra le sue caratteristiche proprio un oggettoStato
. Tutti i partecipanti hanno un nome e un telescopio a cui fanno riferimento per l'osservazione. La nostra classe estendeThread
, e il suo metodorun
definisce l'azione dell'osservazione.
Si noti che, quando viene creato unpackage com.claudiodesio.osservatorio.dati; public class Partecipante extends Thread { private final String nome; private Stato stato; private final Telescopio telescopio; public Telescopio getTelescopio() { return telescopio; } public Partecipante(String nome, Telescopio telescopio) { this.nome = nome; this.telescopio = telescopio; this.setStato(Stato.IN_ATTESA); stato(); } public Stato getStato() { return stato; } public void setStato(Stato stato) { this.stato = stato; } public String getNome() { return nome; } @Override public void run() { telescopio.permettiOsservazione(this); } public void stato() { System.out.println(nome + " dice: " + stato.getMessaggio()); } }
Partecipante
, lo stato viene impostato aIN_ATTESA
. Poi creiamo la classe più importante, ovvero quella che astrae il concetto diTelescopio
:
package com.claudiodesio.osservatorio.dati; public class Telescopio { public synchronized void permettiOsservazione(Partecipante partecipante) { partecipante.setStato(Stato.OSSERVAZIONE); partecipante.stato(); try { Thread.sleep(3000); } catch (InterruptedException ex) { ex.printStackTrace(); } partecipante.setStato(Stato.FINITO); partecipante.stato(); } }
Nel suo metodo di business sincronizzato, si limita a passare da uno stato ad un altro dopo una pausa di 3 secondi.
Infine, segue una classe di test:
package com.claudiodesio.osservatorio.test; import com.claudiodesio.osservatorio.dati.Partecipante; import com.claudiodesio.osservatorio.dati.Telescopio; public class Osservazione { public static void main(String args[]) { Telescopio telescopio = new Telescopio(); Partecipante[] partecipanti = getPartecipanti(telescopio); for (Partecipante partecipante : partecipanti) { partecipante.start(); } } private static Partecipante[] getPartecipanti(Telescopio telescopio) { Partecipante[] partecipanti = { new Partecipante("Ciro", telescopio), new Partecipante("Gianluca", telescopio), new Partecipante("Pierluigi", telescopio), new Partecipante("Gigi", telescopio), new Partecipante("Nicola", telescopio), new Partecipante("Pino", telescopio), new Partecipante("Maurizio", telescopio), new Partecipante("Raffaele", telescopio), new Partecipante("Fabio", telescopio), new Partecipante("Vincenzo", telescopio)}; return partecipanti; } }
L'output è il seguente (che cambia ad ogni lancio):
Ciro dice: "Sono in attesa..." Gianluca dice: "Sono in attesa..." Pierluigi dice: "Sono in attesa..." Gigi dice: "Sono in attesa..." Nicola dice: "Sono in attesa..." Pino dice: "Sono in attesa..." Maurizio dice: "Sono in attesa..." Raffaele dice: "Sono in attesa..." Fabio dice: "Sono in attesa..." Vincenzo dice: "Sono in attesa..." Ciro dice: "Tocca a me... che meraviglia!" Ciro dice: "Ho finito." Vincenzo dice: "Tocca a me... che meraviglia!" Vincenzo dice: "Ho finito." Maurizio dice: "Tocca a me... che meraviglia!" Maurizio dice: "Ho finito." Pino dice: "Tocca a me... che meraviglia!" Pino dice: "Ho finito." Fabio dice: "Tocca a me... che meraviglia!" Fabio dice: "Ho finito." Gigi dice: "Tocca a me... che meraviglia!" Gigi dice: "Ho finito." Nicola dice: "Tocca a me... che meraviglia!" Nicola dice: "Ho finito." Raffaele dice: "Tocca a me... che meraviglia!" Raffaele dice: "Ho finito." Pierluigi dice: "Tocca a me... che meraviglia!" Pierluigi dice: "Ho finito." Gianluca dice: "Tocca a me... che meraviglia!" Gianluca dice: "Ho finito."
-
Esercizio 12.d)
Simulare con del codice funzionante la seguente situazione con quanto appreso in questo capitolo.
Supponiamo di trovarci allo sportello del comune che rilascia le carte d'identità. Supponiamo che ci siano 10 richiedenti in fila pronti per richiedere il documento. Quando arriva il proprio turno, sarà consegnato al richiedente un modulo da compilare. Per non bloccare la fila, il richiedente successivo potrà nel frattempo richiedere il servizio allo stesso sportello. Quindi a tutti i richiedenti sarà consegnato il modulo da compilare, la fila sarò molto veloce, e in parallelo diverse persone saranno occupate a compilare il modulo. Ogni richiedente potrebbe metterci un tempo variabile da 5 a 10 secondi per compilare il modello, il primo che finirà (indipendentemente dalla sua posizione nella fila iniziale) potrà richiedere la stampa della propria carta d'identità. Questa sarà stampata in 3 secondi in ogni caso.
Anche in questo caso usare delle stampe significative per rendere l'esecuzione del programma auto esplicativa.
Soluzione
Creiamo una classe
TimeUtils
che definisce un metodo d'utilità per generare un numero casuale tra 5 e 10:
package com.claudiodesio.sportello.dati; import java.util.*; public class TimeUtils { private static final Random RANDOM = new Random(); public static int getNumeroRandom() { return (RANDOM.nextInt(6) + 5); } }
La classeRichiedente
estendeThread
:
package com.claudiodesio.sportello.dati; public class Richiedente extends Thread { private final String nome; public Richiedente(String nome) { this.nome = nome; } public String getNome() { return nome; } @Override public void run() { Sportello.getInstance().gestisciRichiesta(this); } @Override public String toString() { return nome; } }
La classeStampante
sarà usata per stampare i documenti:
package com.claudiodesio.sportello.dati; public class Stampante { private static Stampante instance; private Stampante() { } public static Stampante getInstance() { if (instance == null) { instance = new Stampante(); } return instance; } public synchronized void stampa(Richiedente richiedente) { System.out.println("Stampa carta d'identità di " + richiedente + " in corso..."); try { wait(3000); } catch (InterruptedException ex) { ex.printStackTrace(); } System.out.println("Stampa completata! " + richiedente + " grazie e arrivederci!"); } }
La classeSportello
è la classe chiave:
package com.claudiodesio.sportello.dati; public class Sportello { private final Stampante stampante; private static Sportello instance; public synchronized static Sportello getInstance() { if (instance == null) { instance = new Sportello(); } return instance; } private Sportello() { stampante = Stampante.getInstance(); } public synchronized void gestisciRichiesta(Richiedente richiedente) { System.out.println("Buongiorno " + richiedente); System.out.println("Impiegato dice: \"Prego compili il modulo " + richiedente + "\""); compilaModulo(richiedente); stampante.stampa(richiedente); System.out.println(richiedente + " dice: \"Grazie a lei!\""); } private synchronized void compilaModulo(Richiedente richiedente) { System.out.println("Richiedente " + richiedente + " dice: \"OK lo compilo subito ma...\""); final int attesa = TimeUtils.getNumeroRandom(); try { System.out.println("...mi servono " + attesa + " minuti..."); wait(attesa * 1000); } catch (InterruptedException ex) { ex.printStackTrace(); } System.out.println("Richiedente " + richiedente + " dice: \"...ho compilato il modulo!\""); } }
Segue la classe di test:
package com.claudiodesio.sportello.test; import com.claudiodesio.sportello.dati.Richiedente; public class TestSportello { public static void main(String args[]) { final Richiedente[] richiedenti = getRichiedenti(); for (Richiedente richiedente : richiedenti) { richiedente.start(); } } private static Richiedente[] getRichiedenti() { Richiedente[] partecipanti = { new Richiedente("Ciro"), new Richiedente("Mario"), new Richiedente("Massimo"), new Richiedente("Chicco"), new Richiedente("Enrico"), new Richiedente("Lorenzo"), new Richiedente("Emanuele"), new Richiedente("Cosimo"), new Richiedente("Alessandro"), new Richiedente("Salvatore")}; return partecipanti; } }
Buongiorno Ciro Impiegato dice: "Prego compili il modulo Ciro" Richiedente Ciro dice: "OK lo compilo subito ma..." ...mi servono 9 minuti... Buongiorno Salvatore Impiegato dice: "Prego compili il modulo Salvatore" Richiedente Salvatore dice: "OK lo compilo subito ma..." ...mi servono 9 minuti... Buongiorno Cosimo Impiegato dice: "Prego compili il modulo Cosimo" Richiedente Cosimo dice: "OK lo compilo subito ma..." ...mi servono 7 minuti... Buongiorno Mario Impiegato dice: "Prego compili il modulo Mario" Richiedente Mario dice: "OK lo compilo subito ma..." ...mi servono 7 minuti... Buongiorno Chicco Impiegato dice: "Prego compili il modulo Chicco" Richiedente Chicco dice: "OK lo compilo subito ma..." ...mi servono 5 minuti... Buongiorno Alessandro Impiegato dice: "Prego compili il modulo Alessandro" Richiedente Alessandro dice: "OK lo compilo subito ma..." ...mi servono 9 minuti... Buongiorno Enrico Impiegato dice: "Prego compili il modulo Enrico" Richiedente Enrico dice: "OK lo compilo subito ma..." ...mi servono 8 minuti... Buongiorno Massimo Impiegato dice: "Prego compili il modulo Massimo" Richiedente Massimo dice: "OK lo compilo subito ma..." ...mi servono 8 minuti... Buongiorno Lorenzo Impiegato dice: "Prego compili il modulo Lorenzo" Richiedente Lorenzo dice: "OK lo compilo subito ma..." ...mi servono 5 minuti... Buongiorno Emanuele Impiegato dice: "Prego compili il modulo Emanuele" Richiedente Emanuele dice: "OK lo compilo subito ma..." ...mi servono 9 minuti... Richiedente Lorenzo dice: "...ho compilato il modulo!" Stampa carta d'identità di Lorenzo in corso... Stampa completata! Lorenzo grazie e arrivederci! Lorenzo dice: "Grazie a lei!" Richiedente Massimo dice: "...ho compilato il modulo!" Stampa carta d'identità di Massimo in corso... Stampa completata! Massimo grazie e arrivederci! Massimo dice: "Grazie a lei!" Richiedente Enrico dice: "...ho compilato il modulo!" Stampa carta d'identità di Enrico in corso... Stampa completata! Enrico grazie e arrivederci! Enrico dice: "Grazie a lei!" Richiedente Mario dice: "...ho compilato il modulo!" Stampa carta d'identità di Mario in corso... Stampa completata! Mario grazie e arrivederci! Mario dice: "Grazie a lei!" Richiedente Cosimo dice: "...ho compilato il modulo!" Stampa carta d'identità di Cosimo in corso... Stampa completata! Cosimo grazie e arrivederci! Cosimo dice: "Grazie a lei!" Richiedente Chicco dice: "...ho compilato il modulo!" Stampa carta d'identità di Chicco in corso... Stampa completata! Chicco grazie e arrivederci! Chicco dice: "Grazie a lei!" Richiedente Emanuele dice: "...ho compilato il modulo!" Stampa carta d'identità di Emanuele in corso... Stampa completata! Emanuele grazie e arrivederci! Emanuele dice: "Grazie a lei!" Richiedente Salvatore dice: "...ho compilato il modulo!" Stampa carta d'identità di Salvatore in corso... Stampa completata! Salvatore grazie e arrivederci! Salvatore dice: "Grazie a lei!" Richiedente Alessandro dice: "...ho compilato il modulo!" Stampa carta d'identità di Alessandro in corso... Stampa completata! Alessandro grazie e arrivederci! Alessandro dice: "Grazie a lei!" Richiedente Ciro dice: "...ho compilato il modulo!" Stampa carta d'identità di Ciro in corso... Stampa completata! Ciro grazie e arrivederci! Ciro dice: "Grazie a lei!"
-
Esercizio 12.e)
Consideriamo le seguenti classi:
import java.util.ArrayList; class RunnableArrayList extends ArrayList implements Runnable { public void run(String stringa) { System.out.println("Nel metodo run(): " + stringa); } } public class Esercizio12E { public static void main(String args[]) { RunnableArrayList g = new RunnableArrayList(); Thread t = new Thread(g); t.start(); } }
Se eseguiamo la classe
Esercizio12E
, quale sarà l'output?1.2.Nel metodo run(): null
Nel metodo run():
3. Il codice viene interrotto con un'eccezione nella classe
Esercizio12E
.
4. Il codice viene interrotto con un'eccezione nella classeRunnableArrayList
.
5. Nessun output. Il codice non compila per un errore nella classeEsercizio12E
.
6. Nessun output. Il codice non compila per un errore nella classeRunnableArrayList
.
Soluzione
La risposta corretta è la numero 6, in quanto la classe
RunnableArrayList
non implementa correttamente l'interfacciaRunnable
, il cui metodo run non prende nessun tipo di parametri in input. Infatti l'output della compilazione è il seguente (sono presenti anche due warning relativi all'utilizzo e alla dichiarazione di raw type):
Esercizio12E.java:3: warning: [rawtypes] found raw type: ArrayList class RunnableArrayList extends ArrayList implements Runnable { ^ missing type arguments for generic class ArrayList<E> where E is a type-variable: E extends Object declared in class ArrayList Esercizio12E.java:3: error: RunnableArrayList is not abstract and does not override abstract method run() in Runnable class RunnableArrayList extends ArrayList implements Runnable { ^ Esercizio12E.java:3: warning: [serial] serializable class RunnableArrayList has no definition of serialVersionUID class RunnableArrayList extends ArrayList implements Runnable { ^ 1 error 2 warnings
-
Esercizio 12.f)
Quali delle seguenti affermazioni sono corrette:
1. Una classe, per creare oggetti che abbiano un monitor, deve estendereThread
.
2. Una classe, per creare oggetti che abbiano un monitor, deve implementareRunnable
.
3. Quando "un thread entra nel monitor dell'oggetto" allora possiede il lock di tale oggetto. Questo significa che nessun altro thread può entrare nel monitor di quell'oggetto.
4. La classeMonitor
è definita dai metodi sincronizzati di una classe.
Soluzione
Solo la risposta 3 è corretta, tutte le altre no.
In particolare, riguardo la risposta 4, la classeMonitor
non esiste. -
Esercizio 12.g)
Quali delle seguenti affermazioni sono corrette?
1. La classeThread
estendeRunnable
.
2.Runnable
è una functional interface.
3. La classeThread
definisce un metodorun
vuoto.
4. La classeThread
definisce i metodiwait
enotify
.
Soluzione
Le risposte corrette sono la numero 2 e la numero 3.
La numero 1 è falsa perché la classeThread
implementaRunnable
. Infatti quest'ultima è un'interfaccia e in quanto tale va implementata e non estesa da un'altra classe.
La numero 4 è sbagliata visto che i metodiwait
enotify
sono dichiarati nella classeObject
. -
Esercizio 12.h)
Creare una classe
ContoAllaRovescia
che una volta attivata fa partire un conto alla rovescia da 10 a 0 prima di terminare.
Soluzione
La classe
ContoAllaRovescia
potrebbe essere la seguente:
public class ContoAllaRovescia { public void attiva(int secondi) throws InterruptedException { for (int i = secondi; i > 0; i--) { System.out.println(i); Thread.sleep(1000); } System.out.println("Tempo scaduto!"); } }
Poi possiamo creare una classe che la testi:
public class Esercizio12H { public static void main(String args[]) throws Exception { ContoAllaRovescia contoAllaRovescia = new ContoAllaRovescia(); int secondi = 10; if (args.length > 0) { try { secondi = Integer.parseInt(args[0]); } catch (Exception exc) { System.out.println("L'input deve essere un numero " + "intero positivo, usiamo il valore di default 10..."); } } contoAllaRovescia.attiva(secondi); } }
Eseguendo quest'ultima senza specificare parametri otterremo il seguente output:
10 9 8 7 6 5 4 3 2 1 Tempo scaduto!
Se passiamo il parametro3
allora l'output sarà limitato a:
3 2 1 Tempo scaduto!
Se invece specifichiamo una lettera (supponiamoF
) avremo il seguente output:
L'input deve essere un numero intero positivo, usiamo il valore di default 10... 10 9 8 7 6 5 4 3 2 1 Tempo scaduto!
Infine, specificando un numero negativo oppure0
, otterremo direttamente il seguente output:
Tempo scaduto!
-
Esercizio 12.i)
Quali delle seguenti affermazioni sono corrette:
1. Per istanziare un thread basta usare i suoi costruttori.
2. Specificare la priorità dei thread non rappresenta un modo efficace per decidere l'ordine di esecuzione di più thread.
3. Per eseguire il metodorun
definito in una classe che estendeThread
basta chiamare il suo metodorun
.
4. Per eseguire un thread bisogna invocare il metodostart
della classeObject
.
5. In ogni programma ci sono almeno tre thread in esecuzione.
Soluzione
Le risposte corrette sono le numero 1, 2 e 3. In particolare la 3 non asserisce che il metodo
run
viene eseguito in un thread a parte, altrimenti sarebbe stata errata. Il metodorun
, rimane comunque un metodo, e quindi è invocabile come qualsiasi altro metodo. L'affermazione numero 4 è errata perché il metodostart
è dichiarato dalla classeThread
, e non dalla classeObject
. L'affermazione numero 5 è errata perché la frase giusta sarebbe: "in ogni programma c'è almeno un thread in esecuzione". -
Esercizio 12.l)
Creare un programma che simuli la seguente situazione. Tramite comandi interattivi (corri, cammina, fermati e basta) letti dalla classe
Scanner
, l'utente reciterà la parte dell'allenatore di un corridore virtuale, dando dei comandi al volo mentre il corridore si allena.
Creare quindi una classeCorridore
che estendeThread
. Essa deve dichiarare i metodi:corri
che gli permetterà di simulare una corsa,cammina
che gli permetterà di simulare una camminata,fermati
che gli permetterà di simulare l'azione del fermarsi, ebasta
che farà terminare l'allenamento (e il programma).Suggerimento: possiamo sfruttare variabili booleane per gestire un ciclo infinito che gestisce il ciclo dell'allenamento.
Soluzione
Le specifiche dell'esercizio 12.l sono volutamente non troppo dettagliate, per lasciare più spazio alla fantasia del lettore. Quindi in questo caso un'eventuale soluzione può differire davvero molto dalla soluzione che presentiamo di seguito.
Per esempio potremmo implementare la classeCorridore
nel seguente modo:
public class Corridore extends Thread { private boolean viaLibera; private boolean inAzione; private int gap; public Corridore() { inAzione = true; gap = 1000; } public void run() { while (inAzione) { try { Thread.sleep(gap); if (viaLibera) { System.out.println("|"); Thread.sleep(gap); System.out.println(" |"); } } catch (InterruptedException exc) { assert false; } } } public void inizia() { start(); } public void corri() { System.out.println("Ok, vado..."); gap = 400; viaLibera = true; } public void fermati() { System.out.println("Ok, mi fermo."); System.out.println("| |"); viaLibera = false; } public void cammina() { System.out.println("Ok, mi riposo un po'..."); gap = 1000; viaLibera = true; } public void basta() { System.out.println("Meno male, non ce la facevo più..."); inAzione = false; } }
Il nucleo della logica di business si trova proprio nel metodorun
, che con un ciclo che si basa sulla variabileinAzione
, stampa con dei simboli|
(si legge "pipe" in inglese) una trama che assomiglia a dei passi. Si noti che quando si deve correre, la variabilegap
, che rappresenta l'attesa da un punto all'altro, è di 400 millisecondi, mentre quando si deve camminare il gap viene allungato ad un secondo. A seconda che sia chiamato il metodocorri
o il metodocammina
quindi, questi passi saranno più veloci o meno veloci.
Ma vediamo ora come potremmo implementare la classe principaleEsercizio12L
:
import java.util.Scanner; public class Esercizio12L { public static void main(String args[]) { Corridore corridore = new Corridore(); corridore.inizia(); Scanner scanner = new Scanner(System.in); boolean cicla = true; System.out.println( "Ciao allenatore, il corridore è a tua disposizione!"); System.out.println("Scrivi i comandi e batti invio"); System.out.println("(corri, cammina, fermati, basta)"); while (cicla) { String comando = scanner.nextLine(); switch (comando) { case"corri": corridore.corri(); break; case"cammina": corridore.cammina(); break; case"fermati": corridore.fermati(); break; case"basta": corridore.basta(); cicla = false; break; default: break; } } try { Thread.sleep(2000); } catch (Exception exc) { assert false; } System.out.println("Fine allenamento"); } }
Il codice non differisce molto da altro che abbiamo già visto tra questi esercizi. Tramite un'oggettoScanner
vengono letti i comandi, che si traducono in chiamate ai metodi diCorridore
.
Se eseguiamo quest'ultima classe potremmo ottenere il seguente output, che non rende bene l'idea se non si vede in azione "dal vivo" (meglio eseguire le classi già pronte dalla cartellaCodice\capitolo_12\esercizi\12.l
del file degli esercizi che avete probabilmente già scaricato insieme a questi esercizi da questo sito):
Ciao allenatore, il corridore è a tua disposizione! Scrivi i comandi e batti invio (corri, cammina, fermati, basta) corri Ok, vado... | | | | | | | | c| a | mmi| na | | Ok, mi riposo un po'... | | | | | ferm| ati | Ok, mi fermo. | | corri Ok, vado... | | | | | | ca| | mm| ina | | | | Ok, mi riposo un po'... | | | | fe | rmat| i Ok, mi fermo. | | basta Meno male, non ce la facevo più... Fine allenamento
Si noti che, i comandi inseriti, si estendono spesso su più righe, perché intanto il programma sta stampando "i passi". -
Esercizio 12.m)
È meglio implementare
Runnable
o estendere la classeThread
? Scegliere tutte le affermazioni corrette.
1. ImplementandoRunnable
che è un'interfaccia, possiamo anche estendere altre classi.
2. Estendendo la classeThread
possiamo anche implementare altre interfacce.
3. Dal punto di vista dell'astrazione dei dati, un oggetto di tipoThread
non dovrebbe possedere variabili private che rappresentano i dati da gestire.
4. Un oggettoRunnable
che implementa il metodorun
, può essere passato in input al costruttore di un oggettoThread
. Se viene invocato su quest'ultimo il metodostart
, viene attivato il thread che esegue il metodorun
.
Soluzione
Tutte le affermazioni sono corrette tranne la numero 2. Infatti anche implementando l'interfaccia
Runnable
si ha la possibilità di implementare altre interfacce. -
Esercizio 12.n)
Quali delle seguenti affermazioni sono corrette?
1. Con il time slicing o round-robin scheduling un thread può trovarsi in esecuzione solo per un certo periodo di tempo.
2. Il time slicing o round-robin scheduling è il comportamento di default della maggior parte dei sistemi Linux.
3. Il preemptive scheduling è il comportamento di default della maggior parte dei sistemi Linux.
4. Con il preemptive scheduling la priorità è un elemento più deterministico rispetto al caso del round robin. Infatti lanciati due thread in contemporanea, il thread a maggiore priorità entrerà nello stato di esecuzione, e ne uscirà solo quando avrà terminato il suo lavoro, oppure nel caso sia chiamato su di esso un metodo come wait o suspend, oppure si metta in attesa di risorse esterne (come nel caso di attesa di risorse di input-output).
Soluzione
Tutte le affermazioni sono corrette tranne la numero 3. Infatti il preemptive scheduling è il comportamento di default della maggior parte dei sistemi Unix.
-
Esercizio 12.o)
Quali delle seguenti affermazioni sono corrette?
1. Il modificatorevolatile
si può usare solo su metodi e variabili.
2. Dichiararevolatile
una variabile d'istanza, implica che saranno consideratevolatile
anche tutte le altre variabili della stessa istanza che vengono usate dal medesimo thread.
3. Per una variabilevolatile
, tutti gli accessi in lettura e scrittura sono atomici.
4. Per una variabile intera nonvolatile
, tutti gli accessi in lettura e scrittura sono atomici.
Soluzione
Tutte le affermazioni sono corrette tranne la numero 1. Infatti il modificatore
volatile
, si può usare solo su variabili d'istanza. -
Esercizio 12.p)
Quali delle seguenti affermazioni sono corrette?
1. La parola chiavesynchronized
permette di dichiarare metodi atomici.
2. Il monitor di un oggetto è costituito dalla parte dell'oggetto sincronizzata. Quindi se in un certo istante, un thread sta eseguendo codice all'interno di uno dei metodi sincronizzati di tale oggetto, qualsiasi altro thread che vuole accedere al codice di uno qualsiasi dei metodi sincronizzati dello stesso oggetto, dovrà attendere che il primo thread termini di eseguire codice sincronizzato.
3. È possibile utilizzare blocchi sincronizzati per rendere sincronizzati solo determinate righe di codice. Questo rappresenta una soluzione più flessibile della sincronizzazione.
4. È possibile utilizzaresynchronized
come modificatore solo con i metodi.
5. È possibile dichiararesynchronized
il metodorun
.
Soluzione
Tutte le affermazioni sono corrette.
-
Esercizio 12.q)
Quali delle seguenti affermazioni sono corrette?
1. I metodiwait
,notify
enotifyAll
sono definiti nella classeThread
.
2. Se un thread incontra il metodowait
abbandona il monitor dell'oggetto di cui stava eseguendo codice.
3. Il metodonotifyAll
fa ripartire tutti i thread che avevano eseguito il metodowait
.
4. I metodisuspend
eresume
sono definiti nella classeThread
.
Soluzione
Tutte le affermazioni sono corrette tranne la numero 1. Infatti metodi
wait
,notify
enotifyAll
sono definiti della classeObject
. -
Esercizio 12.r)
Rendere immutabile la seguente classe:
import java.util.Date; public class Esercizio12RNonImmutabile { private Integer intero; private Date date; public void setDate(Date date) { this.date = date; } public Date getDate() { return date; } public void setIntero(Integer intero) { this.intero = intero; } public Integer getIntero() { return intero; } }
Soluzione
Una possibile soluzione è la seguente:
import java.util.Date; public final class Esercizio12R { private final Integer intero; private final Date date; public Esercizio12R(Integer intero, Date date) { this.intero = intero; this.date = (Date)date.clone(); } public final Date getStringBuilder() { return (Date)date.clone(); } public final Integer getIntero() { return intero; } }
-
Esercizio 12.s)
Quali delle seguenti affermazioni sono corrette riguardo i package java.util.concurrent.atomic e java.util.concurrent.locks?
1.ReentrantLock
è una classe che, se istanziata, può sostituire l'utilizzo di un blocco sincronizzato. Però bisogna utilizzare obbligatoriamente un bloccotry
-catch
-finally
. 2. Il concetto di fairness degli oggetti di tipoReentrantLock
permette alla JVM di creare una gerarchia di priorità di esecuzione dei thread, basata sul tempo di creazione delReentrantLock
. 3. Un oggetto di tipoLock
può specificare un timeout per uscire da un blocco sincronizzato. 4. L'interfacciaAtomicInteger
definisce metodi atomici che compiono più di un'operazione.
Soluzione
Tutte le affermazioni sono corrette tranne la numero 4. Infatti
AtomicInteger
è una classe non un'interfaccia. -
Esercizio 12.t)
Considerato il seguente snippet:
Callable<String> callable = new Callable<>() {public void call(){}}; Future<String> future = Executors.newFixedThreadPool(3).start(callable); String result = future.get();
Che messaggio restituirà il compilatore?
Soluzione
La compilazione dello snippet causerà tre errori, uno per ogni riga:
Esercizio12T.java:6: error: <anonymous Esercizio12T$1> is not abstract and does not override abstract method call() in Callable Callable<String> callable = new Callable<>() {public void call(){}}; ^ Esercizio12T.java:6: error: call() in <anonymous Esercizio12T$1> cannot implement call() in Callable Callable<String> callable = new Callable<>() {public void call(){}}; ^ return type void is not compatible with String where V is a type-variable: V extends Object declared in interface Callable Esercizio12T.java:7: error: cannot find symbol Future<String> future = Executors.newFixedThreadPool(3).start(callable); ^ symbol: method start(Callable<String>) location: interface ExecutorService 3 errors
Infatti nella prima riga abbiamo provato a creare una classe anonima che estendeCallable
parametrizzata con una stringa. Poi viene invocato il metodocall
che ritornavoid
in luogo diString
, che però non esiste. Nella seconda riga l'errore consiste nel chiamare il metodostart
(come perRunnable
), ma il metodo da chiamare èsubmit
. Infine nella terza riga, viene invocato il metodoget
diFuture
che richiede la gestione dell'InterruptedException
.
Quindi, il codice corretto sarebbe simile al seguente:
Callable<String> callable = new Callable<>() { public String call(){return "";} }; ExecutorService service = Executors.newFixedThreadPool(3); Future<String> future = service.submit(callable); String result = null; try { result = future.get(); } catch (Exception exc) { exc.printStackTrace(); }
-
Esercizio 12.u)
Realizzare una sveglia con le classi
Timer
eTimerTask
del packagejava.util
. In particolare deve essere possibile passare da riga di comando un numero che rappresenterà il numero di secondi che devono passare affinché "suoni" la sveglia.
Soluzione
Una possibile (e semplice) soluzione potrebbe essere la seguente:
import java.time.format.*; public class Esercizio12U extends TimerTask { private Timer timer; public Esercizio12U() { timer = new Timer(); } @Override public void run() { DateTimeFormatter timeFormatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT); System.out.println("Sveglia! Sono le " + timeFormatter.format(LocalTime.now())); timer.cancel(); } public static void main(String args[]) throws Exception { int seconds = Integer.parseInt(args[0])*1000; Esercizio12U timerTask = new Esercizio12U(); timerTask.timer.schedule(timerTask, seconds); } }
-
Esercizio 12.v)
Quali delle seguenti affermazioni sono corrette?
1. Lo statement:permette ad un semaforo di acquisire il lock su un oggetto.new Semaphore().acquire();
2. Il metodotryAcquire
permette di specificare un timeout.
3.Semaphore
è un'interfaccia.
4. I permits rappresentano una variabile d'istanza diSemaphore
.
Soluzione
Le risposte corrette sono la numero 2 e la numero 4.
La numero 1 definisce uno statement non compilabile, perché bisogna specificare quantomeno il numero di permits per quanto riguarda il costruttore diSemaphore
.
La numero 3 è falsa perchéSemaphore
è una classe (facile intuirlo visto che viene istanziata con un costruttore). -
Esercizio 12.z)
Quali delle seguenti affermazioni sono corrette?
1. Istanziato un oggettocyclicBarrier
di tipoCyclicBarrier
, lo statement:equivale a chiamare il metodocyclicBarrier.await;
wait
sull'oggetto che legge questa istruzione.
2. Il costruttore diCyclicBarrier
permette di specificare il numero di thread che devono "accumularsi" in un certo punto di codice prima di essere rilasciati.
3. Il metodo signalAll diCyclicBarrier
è equivalente al metodonotifyAll
diObject
.
4. L'utilizzo diCyclicBarrier
può sempre sostituire l'utilizzo diwait
,notify
enotifyAll
.
Soluzione
Le risposte corrette sono le numero 1 e 2.