Anteprima
Vedrai una selezione di 4 pagine su 12
Appunti Esercitazione di Sistemi Operativi Pag. 1 Appunti Esercitazione di Sistemi Operativi Pag. 2
Anteprima di 4 pagg. su 12.
Scarica il documento per vederlo tutto.
Appunti Esercitazione di Sistemi Operativi Pag. 6
Anteprima di 4 pagg. su 12.
Scarica il documento per vederlo tutto.
Appunti Esercitazione di Sistemi Operativi Pag. 11
1 su 12
D/illustrazione/soddisfatti o rimborsati
Disdici quando
vuoi
Acquista con carta
o PayPal
Scarica i documenti
tutte le volte che vuoi
Estratto del documento

Thread in Java

CPUNot Runnable: il thread non può essere messo in esecuzione dallo scheduler. Entra in questo stato quando è in attesa di operazioni di I/O, oppure dopo l'invocazione del metodo sleep(), o del metodo wait(), che verrà discusso in seguito.

Dead: al termine dell'esecuzione del suo metodo run().

Tutti i programmi Java comprendono almeno un thread. Anche un programma costituito solo dal metodo main viene eseguito come un singolo thread.

Java fornisce strumenti che consentono di creare e manipolare thread aggiuntivi nel programma.

La Java Virtual Machine (JVM) è in grado di eseguire una molteplicità di thread su una singola CPU:

  • Lo scheduler della JVM sceglie il thread in stato Runnable con priorità più alta.
  • Se più thread in attesa di eseguire hanno uguale priorità, la scelta dello scheduler avviene con una modalità ciclica (round-robin).
  • Il thread messo in esecuzione dallo scheduler viene interrotto se e solo
se: il metodo run termina l'esecuzione; il thread esegue yield(), si indica allo scheduler che il thread corrente è disposto a fornire l'uso corrente di un processore (metodo statico). un thread con priorità più alta diventa Runnable; il quanto di tempo assegnato si è esaurito. Esistono due modi per implementare thread in Java: - definire una sottoclasse della classe Thread (più facile da usare in applicazioni semplici, ma limitato) - definire una classe che implementa l'interfaccia RunnableThread come sottoclasse di Thread Si definisce una nuova classe che estende la classe Thread e che ridefinisce il metodo run() della classe Thread Si crea un'istanza della sottoclasse tramite new Si chiama il metodo start() sull'istanza creata. Questo determina l'invocazione del thread associato al metodo run() dell'oggetto, e manda in esecuzione il thread. N.B.: L'invocazione diretta del metodo run() non crea un nuovo thread. Thread come

Implementazione di Runnable:

  1. Si definisce una nuova classe che implementa l'interfaccia Runnable e che implementa il metodo run() dell'interfaccia Runnable
  2. Si crea un'istanza della classe tramite new
  3. Si crea un'istanza della classe Thread, passando al suo costruttore un riferimento all'istanza della nuova classe definita
  4. Si chiama il metodo start() sull'istanza della classe Thread creata, determinando l'invocazione del metodo run() dell'oggetto Runnable associato

Classe Thread:

Costruttori:

  • Thread(): crea un nuovo oggetto Thread
  • Thread(String name): crea un nuovo oggetto Thread con nome name
  • Thread(Runnable target): crea un nuovo oggetto Thread a partire dall'oggetto target
  • Thread(Runnable target, String name): crea un nuovo oggetto Thread con nome name a partire dall'oggetto target

Metodi:

  • String getName(): restituisce il nome del Thread
  • long getId(): restituisce un identificativo del Thread
  • void run(): specifica le operazioni svolte dal Thread
Deve essere ridefinito dallasottoclasse, altrimenti non effettua alcuna operazione. Se il Thread è stato costruito a partire da un oggetto Runnable, allora verrà invocato il metodo run di tale oggetto void start(): fa partire l'esecuzione del Thread. Viene invocato il metodo run del Thread. Non può essere invocato più di una volta, nemmeno se il thread ha terminato la sua esecuzione void join() throws InterruptedException: attende fino a quando questo Thread non termina l'esecuzione del proprio metodo run void join(long millis) throws InterruptedException: attende, per un tempo massimo di millis millisecondi, fino a quando questo Thread non termina l'esecuzione del proprio metodo run static void sleep(long millis) throws InterruptedException: determina l'interruzione dell'esecuzione del Thread corrente per un tempo di millis millisecondi boolean isAlive(): restituisce true se il thread è ancora in esecuzione, false altrimenti.

altrimenti static void yield(): determina l'interruzione temporanea del Thread corrente, e consente ad altri Thread di essere eseguiti

static Thread currentThread(): restituisce un riferimento all'oggetto Thread attualmente in esecuzione

Thread.State getState(): restituisce lo stato di un Thread

InterruptedException: eccezione lanciata quando il thread è in stato di waiting, sleeping o è in esecuzione e viene interrotto

L'interruzione di un thread in Java è basata su un meccanismo cooperative. Un thread non può forzare un altro thread ad interrompersi bruscamente, quello che può fare è semplicemente chiedergli di interrompersi nel momento più conveniente per esso.

Nella classe Thread, il meccanismo di interruzione è implementato usando una flag interna conosciuta come interrupt status: il metodo void interrupt() pone a true l'interrupt status e genera una InterruptedException, il metodo boolean isInterrupted() restituisce il

valore corrente dell'interrupt status senza resettarne il valore

Esiste anche il metodo static boolean interrupted() restituisce il valore corrente dell'interrupt status e resetta il suo valore rimettendolo a false.

I Thread demone

Un thread demone è un thread che ha il solo scopo di servire gli altri thread. Quando sono rimasti solo thread demoni in esecuzione, la JVM termina. È possibile trasformare un thread in un demone invocando il metodo setDaemon(true) prima di avviare il thread. Un thread demone non deve mai accedere a una risorsa persistente come un file o un database perché potrebbe essere terminato in qualsiasi momento, anche nel mezzo di un'operazione. Di default un thread appena creato non è demone.

Priorità di un Thread

void setPriority(int p): cambia la priorità del Thread

int getPriority(): restituisce la priorità del Thread

static final int MAX_PRIORITY: la massima priorità (paria 10) che un Thread

può avere static final int MIN_PRIORITY: la minima priorità (pari a 1) che un Thread può avere.

può avere static final int NORM_PRIORITY: la priorità (pari a 5) che viene assegnata di default a un Thread.

Per default, un thread eredita la priorità del thread che lo ha generato. Ogni volta che bisogna scegliere un nuovo thread da eseguire, lo scheduler preferisce quello con priorità più alta. Le priorità dei thread sono altamente dipendenti dal sistema: le priorità dei thread Java sono mappati sui livelli di priorità della piattaforma ospitante, la quale può avere più o meno livelli di priorità.

Accesso ai dati condivisi

L'accesso concorrente a dati condivisi può portare all'inconsistenza dei dati. Per garantire la consistenza dei dati sono necessari meccanismi per assicurare l'esecuzione ordinata dei processi cooperanti.

Del codice è thread-safe se si "comporta correttamente".

anche quando viene utilizzatoda più thread, indipendentemente dal loro scheduling o interleaving (interfogliamento) Obiettivo: proteggere dati condivisi e mutevoli da un accesso concorrenteincontrollato N.B.: Un programma non thread-safe potrebbe funzionare correttamente per anni, ma è comunque errato e potrebbe fallire in qualunque momento Ci sono tre modi per ottenere la thread-safety:
  1. Non condividere dati
  2. Fare in modo che i dati condivisi siano immutabili
  3. Usare un'opportuna sincronizzazione per l'accesso a dati condivisi
Una operazione atomica è una operazione che si completa nella sua interezza senza interruzioni. Nel caso in cui due o più operazioni di aggiornamento (come nel caso del contocorrente) avvengano contemporaneamente, le istruzioni in linguaggio macchina potrebbero risultare interfogliate (interleaved). L'interleaving dipende da come i thread che accedono al contocorrente sono stati schedulati. Race Condition: la situazione incui più processi (o thread) accedono e manipolano dati condivisi in modo concorrente. Il risultato finale dell'esecuzione dei processi dipende dalla temporizzazione o dalla sequenza con cui vengono eseguiti. Per eliminare le race condition, le variabili che possono essere modificate contemporaneamente da più thread devono essere accedute in modo atomico. Sincronizzazione: è la problematica dell'ordinamento temporale di operazioni. Mutua esclusione (Mutual exclusion o mutex): è un tipo di sincronizzazione fra processi (o thread), che serve ad assicurare che solo un processo (o thread) per volta possa trovarsi nella sezione critica. Proprietà di un meccanismo di mutua esclusione: 1. Un solo processo per volta può trovarsi nella sezione critica. 2. Il meccanismo non deve fare ipotesi sulla velocità dei processi. 3. Un processo che non si trovi in sezione critica non deve condizionare un altro processo dall'entrarvi. 4. Nessun processo

può bloccarsi nella sezione critica5. Un processo non deve accusare attesa illimitata per entrare nella sezione critica

Semafori contatori

Sono un meccanismo utilizzato per garantire la mutua esclusione e la sincronizzazione.

Il semaforo contiene una variabile intera (contatore) che può essere incrementata o decrementata esclusivamente mediante i metodi atomici P() e V(), anche chiamati wait() e signal().

Un thread acquisisce (acquire) un semaforo invocando il metodo P(), e lo rilascia (release) invocando il metodo V(). Quando il contatore assume un valore negativo, il semaforo si considera "rosso", ed il thread che invoca il metodo P() viene sospeso. L'invocazione del metodo V() da parte di un thread risveglia un altro thread precedentemente sospeso nel semaforo.

Deadlock – due o più processi (o thread) sono indefinitamente in attesa per un evento che può essere causato da uno soltanto dei processi in attesa.

Starvation = Blocking indefinito. Un

il buffer non venga sovraccaricato (nel caso del produttore) o svuotato (nel caso del consumatore). Per fare ciò, si utilizzano due semafori: uno per tenere traccia del numero di slot liberi nel buffer e uno per tenere traccia del numero di slot occupati. ```html

Il processo potrebbe non essere mai rimosso dalla coda del semaforo in cui è sospeso. Si verifica quando un processo (thread) non riesce ad ottenere un regolare accesso alle risorse condivise e per questo non progredisce.

Semafori binari

Si definisce semaforo binario un semaforo il cui valore intero può valere solo 0 e 1.

Problemi classici:

  • Produttore-consumatore
  • Lettori-scrittori
  • Cinque filosofi
  • Barbiere addormentato

Produttore consumatore

Conosciuto anche con il nome di problema del buffer-limitato, consiste in: Due tipi di processi, produttore e consumatore, che condividono un buffer a dimensione limitata. Il compito del produttore è quello di generare ciclicamente dei dati e metterli nel buffer, quello del consumatore di rimuovere ciclicamente i dati dal buffer, uno alla volta. Bisogna garantire che il buffer non venga sovraccaricato (nel caso del produttore) o svuotato (nel caso del consumatore). Per fare ciò, si utilizzano due semafori: uno per tenere traccia del numero di slot liberi nel buffer e uno per tenere traccia del numero di slot occupati.

```
Dettagli
A.A. 2021-2022
12 pagine
SSD Scienze matematiche e informatiche INF/01 Informatica

I contenuti di questa pagina costituiscono rielaborazioni personali del Publisher stefano-brusco2001 di informazioni apprese con la frequenza delle lezioni di Sistemi operativi e studio autonomo di eventuali libri di riferimento in preparazione dell'esame finale o della tesi. Non devono intendersi come materiale ufficiale dell'università Università della Calabria o del prof Talia Domenico.