|
Appunti informatica |
|
Visite: 1068 | Gradito: | [ Medio appunti ] |
Leggi anche appunti:Il dispositivo PIA Periferal Interface AdapterIl dispositivo PIA Periferal Interface Adapter I campi relativi alla configurazione Progetto per la realizzazione di un robot comandato da computer tramite WiFiIl sistema binarioIl sistema binario Fin dai primi approcci con i calcoli alle |
Sistemi Operativi (JAVA)
Introduzione al linguaggio
Fin dalla sua presentazione il linguaggio Java è stato accolto con molto entusiasmo dai progettisti di software. L'intento iniziale degli sviluppatori era di creare un linguaggio che non dipendesse dall'hardware e che servisse per facilitare lo sviluppo di software integrato in dispositivi elettronici. Java è stato in seguito utilizzato come linguaggio per la rete, per affiancare l'HTML (Hyper Text Markup Language), il quale non è un linguaggio di programmazione vero e proprio, e per offrire quella sicurezza che l'HTML non ha, inoltre, permetteva agli utenti di Internet di utilizzare applicazioni web sicure e indipendenti dalla piattaforma utilizzata.
Il codice sorgente di un programma Java viene compilato in un codice detto Bytecode che è diverso dal linguaggio macchina. Il bytecode è quindi un linguaggio a metà strada tra il linguaggio macchina ed il linguaggio Java comprensibile dagli umani. Con l'uso del bytecode si ha una elevata portabilità, infatti per eseguire un programma Java occorre uno strumento, detto JVM (Java Virtual Machine), che interpreti il bytecode generato dal compilatore Java e lo esegua indipendentemente dal Sistema Operativo utilizzato.
Nota: il Bytecode ha estensione ".class", sarà la JVM a convertirlo in codice eseguibile.
Java è un linguaggio derivato dal C++, fortemente orientato agli oggetti ed ai thread. I vari oggetti si collegano tra loro tramite opportune interfacce. Java è un linguaggio case sensitive, ossia distingue tra caratteri maiuscoli e minuscoli. A differenza del C++, in Java non esistono i puntatori.
I metodi di un oggetto sono le funzioni, interne o di chiamata, dell'oggetto stesso. Qualsiasi programma JAVA deve avere uno ed un solo main ed almeno un oggetto.
Esempio 1:
public class PIPPO //public indica che l'oggetto è accessibile dall'esterno
Esempio 2:
public static void main(string pippo[]) // void indica che questo metodo non restituisce alcun valore in output; string pippo[] è il
// parametro in ingresso; pippo è il nome di un vettore di tipo string
Esempio 3:
System.out.print("HELLO WORLD!!");
//è una chiamata di visualizzazione su schermo che il programma fa al SO, equivale al cout del C.
L'oggetto string è definito nelle librerie standard del compilatore. Per utilizzare altre librerie, si deve usare il comando import.
Dopo aver creato un oggetto, lo si può salvare come "oggetto.java" e, per chiamarlo, si deve digitare sulla shell: java nomeoggetto
Esempio 4:
public class contatore
public void incrementa()
Poiché la variabile val ha attributo private, ci serve un metodo di accesso pubblico che ce ne permetta la lettura (e solo quella!).
Il resto si commenta da solo.
Osserviamo che nell'implementazione di questa classe manca il costruttore: Java, di default, inizializza tutto a zero, inoltre, non è necessario allocare manualmente la memoria per i tipi di dati conosciuti.
Per quanto riguarda la gestione della memoria, Java ha un meccanismo di trash garbage che, periodicamente, controlla quali sono le celle di memoria allocate e non più utilizzate e provvede a liberare lo spazio occupato, in pratica, fa la funzione del distruttore.
Package JAVA
I package sono delle collezioni di classi, racchiuse in una collezione che le accomuna. Sono quindi delle librerie, cui l'utente può accedere e che offrono varie funzionalità. I package possono anche essere creati dall'utente. Il package ha inoltre la funzione di interfaccia tra oggetti, infatti due classi che devono comunicare si devono trovare nello stesso package.
Prima dell'implementazione di ogni classe comunicante si deve inserire la stringa:
package nome_package;
Esempio 5:
Dato un vettore di 5 elementi, assegnarvi dei valori casuali compresi tra 1 e 10, stamparlo, ordinarlo e stamparlo di nuovo.
Il main deve essere contenuto all'interno di una classe, per cui ne creiamo una vuota fittizia.
Public class lezione2
Implementiamo ora la classe vettore:
package pippo;
public class vettore;
Nota: la dimensione del vettore avrebbe anche potuto essere un parametro in ingresso, ed in Java non deve necessariamente essere passato per riferimento costante.
Public void riempiti() per 10 ed aggiungere 1 per escludere
//lo 0 e comprendere il 10.
Il parametro int serve per far restituire un intero. Si sarebbe potuto fare con un'altra funzione, Math.round(), che effettua un arrotondamento delle cifre, ma ci sarebbe stato un errore nella conversione del tipo, infatti, il valore restituito dalla funzione random() è double, il suo arrotondamento è un long, e noi vogliamo un int.
Questo brutale troncamento viola il principio della equità delle soluzioni, ma per semplicità lo ignoriamo.
public void stampati()
public void ordinati()
private void scambia()
In Java non si può scegliere il tipo di passaggio (per valore o per riferimento): i tipi predefiniti sono passati per valore; gli oggetti per riferimento. Se si vuole passare un oggetto per valore, si deve fare manualmente una copia di esso per poterci lavorare.
Gestione delle eccezioni
Le eccezioni sono un modo per controllare gli errori, senza confondere il codice con tante istruzioni di controllo dell'errore. Quando si verifica una situazione di errore viene lanciata una eccezione, che se viene in seguito catturata permette di gestire l'errore; altrimenti viene eseguita, dal supporto a tempo di esecuzione di Java, una routine di default.
Le eccezioni sono degli oggetti appartenenti alla classe Exception, quindi possono essere trattate come veri e propri componenti del linguaggio. Nella programmazione in Java è obbligatorio usarle, infatti c'è sempre la possibilità di situazioni anomale.
Vi sono tre tipologie di errori: errori di compilazione; errori logici; errori di runtime.
Esempio 6:
class lezione3
Compilando questo programma ci potrebbe essere un errore, infatti la chiamata system.in.read() può generare un'eccezione (ad esempio se legge qualcosa di diverso da un intero). Per "catturare" l'eccezione si usano i comandi try e catch
Rivediamo il codice:
class lezione3
catch (Exception e)
system.out.print(a);
}
Si può essere più precisi nel tipo di eccezione, ad esempio scrivendo (IO Exception e), ma Java non conosce IO Exception, e lo si deve includere nella libreria apposita.
JAVA |
IO |
UTIL |
NET |
In Java le librerie sono inserite in packages collegati tra loro con una struttura ad albero. La libreria principale si chiama java. Per importare una libreria si deve definire il precorso.
Ad esempio, scrivendo all'inizio della classe: Import java.io.* si importa la libreria IO e tutto il suo sottoalbero, ma non viene importata ad esempio la libreria NET. Scrivendo Import java.* si sarebbero importate anche tutte le altre librerie.
pippo |
vettore.class |
lezione2.class |
ad esempio, per far vedere l'oggetto vettore da un'altra classe, basta scrivere: import pippo vettore
Nota: se chiamiamo un package java pippo, pippo diventa un figlio di java.
Ripasso sulle classi
Le classi sono dei prototipi di oggetti, ovvero sono delle strutture astratte che possono essere instanziate, e quindi creare uno o più oggetti. La classe definisce tutte le proprietà (attributi) degli oggetti appartenenti alla classe stessa, e le funzioni (metodi) che verranno usate per agire su di essi.
Introduciamo adesso degli attributi e dei metodi particolari, i cosiddetti membri statici. Per come abbiamo definito i membri della classe non ci è possibile referenziare direttamente dalla classe attributi e metodi, questo perché essi lavorano su una istanza della classe, ovvero su un oggetto, però a volte può essere utile scrivere metodi e attributi che possano essere invocati senza dover istanziare l'oggetto, ma direttamente dalla classe, per fare ciò occorre dichiararli static, ad esempio:
class TitoliAziendaW
TitoliAziendaW(String nome)
Si può decidere ad esempio di istanziare un oggetto della classe TitoliAziendaW solo se conviene, ad esempio scrivendo:
if(TitoliAziendaW.InteressiMaturati(12)>1000)CompraAzioni(10000);
Dove CompraAzioni(int X) è una funzione che istanzia X TitoliAziendaW.
Esempio 7:
class Studente
int cont =0;
studente (string nome)
Vediamo cosa succede senza usare static:
s1 |
|
|
uno |
cont matricola nome |
s2 |
|
|
due |
cont matricola nome |
system.out.print(s1.incrementa())
Nota: l'output restituisce il valore 2, poiché prima da un valore e poi lo incrementa.
Se è necessario che un metodo venga chiamato da una classe, esso deve essere statico. All'interno di metodi statici si può lavorare solo con variabili statiche. Il main è statico perché è chiamato dall'esterno, non su un'istanza. Allo stesso tempo un metodo statico può essere chiamato su un'istanza.
Ad esempio:
static int incrementa()
Ereditarietà delle classi
Per creare una classe Studente ed una classe Studente_Lavoratore che differiscano solo per un campo, si hanno due possibilità: creare due classi distinte (ma, oltre che sprecare risorse, si va contro la programmazione a oggetti); o definire la classe Studente_Lavoratore a partire dalla classe Studente già esistente, sfruttando l'ereditarietà delle classi.
class Studente_Lavoratore extends Studente
L'oggetto così creato eredita tutte le proprietà e i metodi della classe Studente.
Nota: l'attributo protected fa sì che una classe sia accessibile solo da quelle ereditate.
Thread
I thread sono entità che lavorano in parallelo (in realtà lavorano sequenzialmente per frazioni di tempo così da simulare un lavoro in parallelo), sono usati ad esempio in algoritmi per la gestione della concorrenza. Per creare un oggetto di questo tipo bisogna scrivere "extends Thread".
incrementatore |
incrementatore |
contatore int valore |
L'oggetto incrementatore legge il contatore e lo aumenta di 1.
Si vuole che le due istanze di incrementatore (oggetti che accedono alla stessa risorsa) lavorino contemporaneamente, con i linguaggi procedurali, ciò non è possibile.
public class contatore
public void incrementa()
public int get()
class Incrementatore extends thread
Public void run() //notare che il run è ciò che il thread farà nel suo tempo di esecuzione.
Ricordiamo che il main può stare in una delle due classi (incrementatore o contatore) oppure in una classe fittizia.
public class provathread
Avendo due incrementatori che aggiornano 100000 volte un contatore inizializzato a zero, ci si aspetta un valore di 200000; ma, in realtà, si ha un valore inferiore a causa di alcuni problemi di concorrenza tra processi. Prove diverse daranno risultati diversi! Tutti i risultati <=200000 sono equiprobabili, il risultato dipende dalla CPU, dal sistema operativo, dagli altri programmi in esecuzione, ecc.
Il problema nasce dalla funzione incrementa(), dove è stata usata la variabile tmp, ma anche usando la variabile val++ questo problema sussiste, anche se in casi più rari.
Per una corretta programmazione non si può ignorare la concorrenza tra processi. Non è possibile prevedere quando la CPU passa da un thread all'altro, ed in particolare, se il passaggio avviene nel "blocco critico". Una volta individuata la sezione critica si indica al compilatore, tramite la parola chiave synchronized (simile a un semaforo), di non interrompere l'esecuzione in quel punto.
Il synchronized serve (in caso di multiprogrammazione, ossia con i thread) per indicare che si può accedere ad un metodo in mutua esclusione. Questa parola, va usata con parsimonia, poiché peggiora le prestazioni. Usare il synchronized è come mettere un lucchetto sulla risorsa condivisa.
Ad esempio:
public synchronized void incrementa()
Esempio per migliorare le prestazioni: si hanno 500 righe di codice, ma solo 3 di queste possono creare problemi, in questo caso si ha un eccessivo decremento di prestazioni se si usa il synchronized su tutte e 500 le righe di codice, per evitare ciò si mettono le 3 righe che possono dare problemi tra parentesi graffe e si usa la parola synchronized solo per questo blocco.
.
.
synchronized(c)
.
si deve passare un'istanza di cui il thread controllerà il lock.
public void incrementa()
Le due forme sono equivalenti: se si usa il synchronized a livello di metodo, il this è sottointeso.
Sincronizzazione
Un altro problema è quello della sincronizzazione. Si potrebbe gestire con un monitor, ma è troppo complicato, e si preferisce risolverlo con qualche funzione in più.
Esempio: sia T un'istanza di un oggetto derivato da una classe thread.
T.sleep(tempo_in_millisec) indica per quanti ms il thread non deve fare nulla.
Questo metodo può generare delle eccezioni e quindi non avviarsi più, ad esempio ciò può avvenire se quando non fa nulla viene bloccato.
La soluzione è scrivere:
try
catch(Exception e);
Esempio 8 (produttore-consumatore):
Si considerino 1 entità produttore, 1 entità consumatore ed un buffer di interi. Produttore e consumatore devono essere thread perché agiscono in contemporanea. Il produttore impiega un tempo random (<1sec) per produrre un intero e che ne produca 10 in un intervallo che va da 1 a 10. Il consumatore impiega un tempo random (<2sec) per stampare su video il valore della cella "occupata" del buffer, "liberandola".
public class Produttore extends Thread
public void run() catch(Exception e) ;
b.Inserisci((int)(Math.random()*100));
}
public class Consumatore extends Thread
public void run() catch(Exception e) ;
b.estrai();
}
}
public class Principale
public class Buffer;
public void inserisci(int a)
public int estrai();
Nota: la classe Buffer è sbagliata!! Si potrebbero inserire operazioni di output su video.
Poiché il consumatore ha un tempo di esecuzione doppio rispetto al produttore, può accadere che salti alcuni valori e legga più volte l'ultimo.
In questo caso non è importante la mutua esclusione, ma la sincronizzazione. Scrivendo solo synchronized, si peggiorano i tempi di esecuzione senza risolvere il problema.
Scriviamo la classe Buffer corretta:
class Buffer;
public synchronized void inserisci(int a)catch (Exception e);
valore = a;
leggere = true;
notify All();
}
public synchronized int estrai()catch (Exception e); //impegna la CPU, notare che anche il wait può
leggere = false; //generare eccezioni
notify All(); il comando notify() sveglia gli eventuali thread in attesa.
return a;
}
Non occorre inserire un else perché quando si sveglia il thread, deve compiere l'istruzione successiva. La coda dei processi in attesa è unica; quindi se un thread si sveglia e non può essere eseguito, per una questione di priorità, viene bloccato.
Quando su un metodo è invocato il wait() rilascia il lock sulla risorsa rendendola di nuovo disponibile.
P1 |
|
Produttore1 |
Produttore2 |
P2 |
|
Consumatore |
In tal caso il numero di valori creati dai due produttori deve essere uguale al numero di prodotti usati dal consumatore.
Esaminiamo un problema: P1 viene interrotto dalla CPU dopo aver letto un certo valore val nel buffer. P2 ha tutto il tempo di eseguire il suo ciclo di istruzioni, cioè aggiorna il contatore al valore val+1. Quando P1 si risveglia, non fa nuovamente il controllo del valore nel buffer, incrementa il valore che aveva precedentemente letto, e questo può essere fonte di errore.
La soluzione più elegante sarebbe creare due monitor.
A scapito dell'efficienza, di solito si utilizza il synchronized e si obbliga il processo P1 a rifare il test quando viene riattivato, ossia si sostituisce l'if con un while. Si usa il while anche quando ci sono più consumatori.
Può, inoltre, accadere che tutti i processi siano in attesa e che nessuno possa Riattivarli. Si ha la possibilità di fare un notify All(), ossia di risvegliare tutti i processi in attesa.
Nota: bisogna mettere il notify All() in tutti e due i metodi.
Normalmente il while si usa col notify All() e l'if col notify().
Esercitazione (compito del 19-03-02)
Si considerino 3 linee ferroviarie L1L3, con un tronco T . Le linee sono unidirezionali e tutte nello stesso senso. Per ogni linea Li vi è un tratto di ingresso e un tratto di uscita. Un treno proveniente dalla linea Li (che occupa quindi il tratto di ingresso di Li) può inserirsi nel tronco T solo se entrambe le seguenti condizioni sono verificate: 1) T è libero e 2) il tratto di uscita di Li è libero. Si modelli la situazione sopra descritta utilizzando il multi-threading di Java (ogni treno è un thread, identificato da un codice) e il costrutto monitor. Si doti il programma del metodo main per testare il modello. Si creino 5 istanze di treno, che scelgano in maniera random la linea da percorrere e la percorrano in entrambi i tratti (attraversando il tronco). Si simuli la percorrenza con un'attesa random compresa tra 1 e 3 sec.
public class treno extends Thread
public void run() catch(Exception e);
T.attraversa(linea); //occupa tronco
try catch(Exception e);
T.rilascia(linea); //rilascia tronco
try catch(Exception e);
T.uscita(linea); //libera linea
public class tronco
public synchronized void attraversa(int l)catch(Exception e) ;
libero=false;
}
public synchronized void rilascia(int l)
public synchronized void uscita(int l)
public static void main(string arg v[])
Compito del 13-09-02
Esercizio 1. Si consideri un aeroporto dotato di un'unica pista. Supponendo di voler gestire in mutua esclusione il solo arrivo degli aeroplani, si modelli la situazione sopra descritta utilizzando il multi-threading di Java (ogni aeroplano e' un thread) e il costrutto monitor.
Si doti il programma del metodo main (corredato degli opportuni messaggi a video) per testare il modello. Si creino 12 istanze di aeroplano che chiedano di atterrare dopo un tempo casuale minore di 16 sec. e occupino la pista (solo se essa è libera) per l'atterraggio per un tempo casuale inferiore a 4 secondi.
Esercizio 2. Si consideri un aeroporto dotato di due piste, una destinata prevalentemente per il decollo, l'altra prevalentemente per l'atterraggio (una pista destinata prevalentemente per il decollo può essere utilizzata per l'atterraggio se e solo se 1) non ci sono aeroplani che abbiano richiesto la pista per decollare; 2) il numero di aeroplani che abbiano richiesto di atterrare è superiore a 3; analoga definizione vale per la pista destinata prevalentemente per l'atterraggio).
Supponendo di voler gestire in mutua esclusione l'arrivo e la partenza degli aeroplani, si modelli la situazione sopra descritta utilizzando il multi-threading di Java (ogni aeroplano e' un thread) e il costrutto monitor.
Si doti il programma del metodo main (corredato degli opportuni messaggi a video) per testare il modello. Si creino 15 istanze di aeroplano parcheggiate nell'aeroporto che scelgano di decollare dopo un tempo casuale minore di 50 sec. e 21 istanze di aeroplano già in volo che, dopo un tempo casuale minore di 30 sec, richiedano alla torre di controllo di atterrare. Il tempo di atterraggio o decollo può essere simulato con un tempo casuale inferiore a 8 secondi.
public class Traccia2
}
}
public class Aereo extends Thread
public void run() catch(Exception e) ;
} else catch(Exception e) ;
}
Pista=ap.richiesta(volo);
try catch(Exception e) ;
ap.rilascia(Pista);
}
public class Aeroporto
public synchronized boolean richiesta(boolean volo) else if(PistaDecolloLibera && RichiesteDecollo==0 && RichiesteAtterraggio>3) else
try catch(Exception e) ;
}
} else else if(PistaAtterraggioLibera && RichiesteDecollo>3 && RichiesteAtterraggio==0) else try catch(Exception e) ;
}
}
}
public synchronized void rilascia(boolean UsataPistaAtterraggio) else
notifyAll();
}
Compito del 18-03-03
public class Auto extends Thread
public void run() catch(Exception e) ;
if(Telepass) catch(Exception e) ;
ct.rilascia();
else catch(Exception e) ;
c.rilascia(Postazione);
}
}
public class Casello
public synchronized boolean occupa() catch(Exception e) ;
if(Postazione1Libera)
else
}
public synchronized void rilascia(boolean Postazione1)
else
notify();
}
public class CaselloTelepass
public synchronized void occupa() catch(Exception e) ;
PostazioneTelepassLibera = false;
System.out.print('Postazione Telepass occupata');
System.out.println();
}
public synchronized void rilascia()
public class Traccia2
}
Appunti su: |
|