Laboratorio di Sistemi Thread Java



Похожие документы
Multithreading in Java. Fondamenti di Sistemi Informativi

Java threads (2) Programmazione Concorrente

Java Virtual Machine

Tipi primitivi. Ad esempio, il codice seguente dichiara una variabile di tipo intero, le assegna il valore 5 e stampa a schermo il suo contenuto:

Telematica II 17. Esercitazione/Laboratorio 6

T 1. Per un processo con più thread di controllo, lo stato di avanzamento della computazione di ogni thread è dato da:

GESTIONE DEI PROCESSI

19. Introduzione al multi-threading

Programmazione concorrente in Java. Dr. Paolo Casoto, Ph.D

Ottava Esercitazione. introduzione ai thread java mutua esclusione

Uso di JUnit. Fondamenti di informatica Oggetti e Java. JUnit. Luca Cabibbo. ottobre 2012

Programmazione concorrente in Java

SAPIENZA Università di Roma Facoltà di Ingegneria dell Informazione, Informatica e Statistica

Un esercizio d esame. Flavio De Paoli

Gestione dei thread in Java LSO 2008

I Thread. I Thread. I due processi dovrebbero lavorare sullo stesso testo

Tale attività non è descritta in questa dispensa

Esercizi su. Funzioni

Il costrutto monitor [Hoare 74]

A intervalli regolari ogni router manda la sua tabella a tutti i vicini, e riceve quelle dei vicini.

Inizializzazione, Assegnamento e Distruzione di Classi

Concetto di Funzione e Procedura METODI in Java

Alla scoperta della nuova interfaccia di Office 2010

Lezione 15 Programmazione multithreaded

Realizzazione di Politiche di Gestione delle Risorse: i Semafori Privati

Università di Torino Facoltà di Scienze MFN Corso di Studi in Informatica. Programmazione I - corso B a.a prof.

Programmare in Java. Olga Scotti

Java: Compilatore e Interprete

Introduzione alla programmazione in C

Esercizio 1: trading on-line

Gestione della memoria centrale

13 - Gestione della Memoria nella Programmazione Orientata agli Oggetti

Pronto Esecuzione Attesa Terminazione

1. Che cos è la multiprogrammazione? Si può realizzare su un sistema monoprocessore? 2. Quali sono i servizi offerti dai sistemi operativi?

Con il termine Sistema operativo si fa riferimento all insieme dei moduli software di un sistema di elaborazione dati dedicati alla sua gestione.

Istruzioni per la configurazione di IziOzi

Introduzione ai Metodi Formali

Database 1 biblioteca universitaria. Testo del quesito

Un sistema operativo è un insieme di programmi che consentono ad un utente di

NUOVA PROCEDURA COPIA ED INCOLLA PER L INSERIMENTO DELLE CLASSIFICHE NEL SISTEMA INFORMATICO KSPORT.

risulta (x) = 1 se x < 0.

La prima applicazione Java. Creazione di oggetti - 1. La prima applicazione Java: schema di esecuzione. Gianpaolo Cugola - Sistemi Informativi in Rete

T E O R I A D I P R O G E T T A Z I O N E D E L S O F T W A R E

FOXWave Gestione gare ARDF IZ1FAL Secco Marco Sezione ARI BIELLA

Synchronized (ancora)

progam ponteasensounicoalaternato ; type dir = ( nord, sud );

Il costrutto monitor [Hoare 74]

Monitor. Introduzione. Struttura di un TDA Monitor

3 - Variabili. Programmazione e analisi di dati Modulo A: Programmazione in Java. Paolo Milazzo

Introduzione. Classificazione di Flynn... 2 Macchine a pipeline... 3 Macchine vettoriali e Array Processor... 4 Macchine MIMD... 6

Automatizzare i compiti ripetitivi. I file batch. File batch (1) File batch (2) Visualizzazione (2) Visualizzazione

1 Processo, risorsa, richiesta, assegnazione 2 Concorrenza 3 Grafo di Holt 4 Thread 5 Sincronizzazione tra processi

Siamo così arrivati all aritmetica modulare, ma anche a individuare alcuni aspetti di come funziona l aritmetica del calcolatore come vedremo.

MODELLO CLIENT/SERVER. Gianluca Daino Dipartimento di Ingegneria dell Informazione Università degli Studi di Siena

Workland CRM. Workland CRM Rel /11/2013. Attività --> FIX. Magazzino --> NEW. Nessuna --> FIX. Ordini --> FIX

Esercizi sul Monitor in Java

La gestione dell input/output da tastiera La gestione dell input/output da file La gestione delle eccezioni

Appunti di Informatica 1

Esercizi sugli Oggetti Monitor

CREAZIONE DI UN AZIENDA

SOMMARIO Coda (queue): QUEUE. QUEUE : specifica QUEUE

Mac Application Manager 1.3 (SOLO PER TIGER)

Capitolo 2. Operazione di limite

COSTER. Import/Export su SWC701. SwcImportExport

Il Sistema Operativo. C. Marrocco. Università degli Studi di Cassino

Scheduling. Sistemi Operativi e Distribuiti A.A Bellettini - Maggiorini. Concetti di base

10 - Programmare con gli Array

INTRODUZIONE AGLI ALGORITMI INTRODUZIONE AGLI ALGORITMI INTRODUZIONE AGLI ALGORITMI INTRODUZIONE AGLI ALGORITMI


Gestione Risorse Umane Web Work-flow Selezione

Algoritmi di Ricerca. Esempi di programmi Java

Corso di Sistemi di Elaborazione delle informazioni

Prova di Laboratorio di Programmazione

Java:Struttura di Programma. Fabio Scanu a.s. 2014/2015

Esercizi della lezione 5 di Java

f(x) = 1 x. Il dominio di questa funzione è il sottoinsieme proprio di R dato da

Siti web centrati sui dati Architettura MVC-2: i JavaBeans

APPUNTI DI MATEMATICA LE FRAZIONI ALGEBRICHE ALESSANDRO BOCCONI

Le variabili. Olga Scotti

Approccio stratificato

Leggere un messaggio. Copyright 2009 Apogeo

INVIO SMS

Terza Esercitazione. Unix - Esercizio 1. Unix System Call Exec Java Introduzione Thread

Esercitazione n 4. Obiettivi

La concorrenza in Java package java.util.concurrent Antonio Furone

Programmazione Orientata agli Oggetti in Linguaggio Java

Arduino: Programmazione

Informatica 3. Informatica 3. LEZIONE 6: Il controllo dell esecuzione. Lezione 6 - Modulo 1. Errori durante l esecuzione. Il controllo dell esecuzione

Figura 1 Le Icone dei file di Excel con e senza macro.

ASPETTI GENERALI DI LINUX. Parte 2 Struttura interna del sistema LINUX

Invio SMS. DM Board ICS Invio SMS

Il problema del produttore e del consumatore. Cooperazione tra processi

INSTALLAZIONE NUOVO CLIENT TUTTOTEL (04 Novembre 2014)

Università per Stranieri di Siena Livello A1

RILANCIO ALLARMI SU SWC701

Sistemi Operativi. Lez. 13: primitive per la concorrenza monitor e messaggi

Транскрипт:

Premessa Fare più di una cosa contemporaneamente per noi è una cosa piuttosto comune. La vita quotidiana è piena di esempi che ci possono chiarire il significato dell'avverbio contemporaneamente. Ad esempio quando facciamo colazione con latte, caffè, biscotti, burro e marmellata (per una colazione all'italiana) oppure con uova, pancetta, toast, burro ecc (per una colazione alla tedesca), noi di solito alterniamo diverse attività: sorseggiamo un po' di latte, spalmiamo un biscotto di marmellata e lo mangiamo e così via. Se telefona un nostro amico, magari ci alziamo, parliamo con lui al telefono continuando a bere il latte. Cioè eseguiamo tutte queste attività nello stesso momento, alternandole opportunamente (ciascuna attività avanza concorrentemente assieme alle altre evolvendo). Processi pesanti e processi leggeri I programmi finora scritti in java (per esempio le applicazioni console ) hanno un solo flusso di esecuzione che procede partendo dall'alto via via verso il basso. Tuttavia siamo anche abituati, quando lavoriamo o ci divertiamo con un pc, a svolgere mille altre attività: per esempio guardiamo la posta, poi navighiamo su Internet, scriviamo un documento Word, tutto questo sempre in maniera parallela. Tutte queste funzionalità ci vengono garantite, oltre che dai programmi, dal nostro sistema operativo che associa ad ogni applicativo che abbiamo installato sul nostro calcolatore un processo in esecuzione (dei processi abbiamo parlato lo scorso anno). A voler essere più precisi, in realtà occorre distinguere tra processi pesanti e processi leggeri. Sono detti processi pesanti i processi ai quali siamo abituati (per esempio Word od Excel, Firefox, Outlook o Evolution ecc, una volta che ovviamente abbiamo provveduto a caricarli in memoria), ciascuno col suo flusso di esecuzione e i suoi dati. Sono detti invece processi leggeri tutti quei flussi di esecuzione che, ad esempio, sono attivi all'interno di uno stesso programma (per cui ne condividono anche i dati) e che sono pensati per assolvere a dei servizi particolari. Se stiamo navigando con Firefox, possiamo aprire una nuova tab all'interno della quale caricare una nuova pagina web. Oppure se stiamo scrivendo un documento, è un processo leggero la routine di salvataggio automatico del file sul quale stiamo lavorando, oppure la routine di controllo ortografico. Definizione Proviamo a dare una definizione generale di thread: Un thread (o flusso di esecuzione o flusso di controllo oppure contesto di esecuzione ) è una singola sequenza di istruzioni all'interno di uno stesso programma. Nei programmi console java, il flusso di esecuzione comincia con la prima istruzione contenuta nel metodo main(), poi si prosegue con l'istruzione successiva (ad esempio una istruzione while), oppure procediamo su un certo ramo di una if, piuttosto che su un altro, eseguiamo un ciclo for e così via. In poche parole, siamo in grado di individuare un certo percorso, per cui, se idealmente tracciamo con una matita il percorso compiuto dal flusso, l'immagine che ne viene fuori è quella appunto di un filo (piuttosto aggrovigliato) da qui il termine thread (thread tradotto in italiano significa proprio filo ). Allo stesso modo, nelle applet, il flusso di esecuzione comincia invece con la prima istruzione del metodo init(), prosegue con la successiva, ma ciò non cambia la sostanza del ragionamento. thread.pdf Pag. 1/10 Cozzetto

Parti di un thread Un thread risulta composto da tre parti: una CPU virtuale il codice che la CPU esegue l'area dati su cui il codice lavora Fig. 1 Un thread o contesto di esecuzione Due esempi piuttosto evidenti di thread sono la JVM stessa e il Garbage Collector. Il motivo del successo dei thread in java è senza dubbio legato a due fattori essenziali: la relativa facilità con la quale si gestisce il codice il supporto già nativo per i thread (la classe Thread che si deve usare per programmare i thread è già inclusa nel package java.lang) Accesso al thread corrente Nel momento in cui scriviamo un programma java, abbiamo già individuato un thread: quello associato al metodo public static void main(). Posso ottenere delle informazioni specifiche relative al thread corrente; in particolare il suo identificativo, il suo nome, la sua priorità (un inero compreso tra 1 e 10), il nome del gruppo cui il thread appartiene. * Dimostra come accedere al thread corrente public class CurrentThread { * Il metodo run dell'applicazione * @param args Argomenti da riga di comando public static void main(string args[]) { Thread current = Thread.currentThread(); System.out.println( String.format("ID: %d\nnome: %s\npriorita': %d\nnome Gruppo: %s", current.getid(), current.getname(), current.getpriority(), current.getthreadgroup().getname())); thread.pdf Pag. 2/10 Cozzetto

Compilando ed eseguendo la classe, dovremmo ottenere un risultato simile a questo: ID: 1 Nome: main Priorità: 5 Nome Gruppo: main Creare thread in Java Ci sono sostanzialmente due modi per creare un thread in java: 1. Estendendo la classe Thread 2. Implementando l'interfaccia Runnable Modalità 1 Consideriamo un semplice esempio di thread: immaginiamo di assegnare a un certo thread un numero e facciamogli stampare quel numero un certo numero di volte, per esempio 10. Nella modalità 1, il thread è creato estendendo la classe Thread (che contiene il metodo public void run() ed è vuoto) e sovrascrivendo quel metodo. Ecco il codice della classe: Fig. 2 La classe NumberThread estende Thread public class NumberThread extends Thread { int num; public NumberThread(int n) { num = n; public void run() { for (int k=0; k < 10; k++) { System.out.print(num); // for // run() // NumberThread class Scriviamo ora una classe di prova: public class Numbers { public static void main(string args[]) { // Definisco i reference di 5 threads NumberThread number1, number2, number3, number4, number5; thread.pdf Pag. 3/10 Cozzetto

// Creo e faccio partire i 5 thread number1 = new NumberThread(1); // il metodo start() richiama poi il metodo run() number1.start(); // la stessa cosa per gli altri thread number2 = new NumberThread(2); number2.start(); number3 = new NumberThread(3); number3.start(); number4 = new NumberThread(4); number4.start(); number5 = new NumberThread(5); number5.start(); // main() // fine Numbers class Fig. 3 Un oggetto di tipo Numbers crea 3 thread Eseguiamo la classe Numbers: l'output dovrebbe essere simile al seguente: 11111111112222222222333333333344444444445555555555 Ogni thread giunge a conclusione esattamente nell'ordine di chiamata. Cosa accade se invece incrementiamo il numero di iterazioni (per esempio 200)? Le cose cambiano di molto. L'output potrebbe essere simile al seguente: 111111111111111111111111111111111111111111111111111111111111111111111 111111111111111111111111111111111111111111111111111111111111111111111 111111111111111111111111111111111111111111111111111111111111112222222 222222222222222222222222222222222222222222222222222222222222222222222 222222222222222222222222222222222222222222222222222222222222222222222 222222222222222222222222222222222222222222223333333333333333333333333 333333333333333333333333333333333333333333333333333333333333333333333 333333333333333333333333333444444444444444444444444444444444444444444 444444444444444444444444444444444444444444444444444444444444444444444 444444444455555555555555555555555555555555555555555555555555555555555 555555555555555555555555555555555555555555555555555555555555552222222 222233333333333333333333333333333333333333333333333333333333333333333 333333333333334444444444444444444444444444445555555555555555555555555 555555555555555555555555555555555555555555555555555555444444444444444 4444444444444444444444444444444444 Solo il thread 1 giunge in effetti a conclusione dopo essere stato lanciato, mentre gli altri 4 no. thread.pdf Pag. 4/10 Cozzetto

L'ordine e il tempo di esecuzione dei thread non è predicibile. E' vero che i 3 thread sono stati avviati uno di seguito all'altro, ma ciò non implica che l'ordine di esecuzione sia esattamente quello indicato dalla sequenza delle chiamate del metodo start()! Questo perchè è lo scheduler dei thread a decidere quale thread far partire per primo ed è sempre lui che deciderà quando interrompere l'esecuzione di un thread e cedere il controllo a un altro thread! Modalità 2 L'altra modalità consiste nell'implementare il metodo run() dell'interfaccia Runnable, come nella figura riportata di seguito. Fig. 4 La classe NumberPrinter implementa l'interfaccia Runnable Scriviamo il codice delle classi in questo modo: public class NumberPrinter implements Runnable { int num; public NumberPrinter(int n) { num = n; public void run() { for (int k=0; k < 10; k++) System.out.print(num); // run() // NumberPrinter class public class Numbers { public static void main(string args[]) { Thread number1, number2, number3, number4, number5; // Create and start each thread number1 = new Thread(new NumberPrinter(1)); number1.start(); number2 = new Thread(new NumberPrinter(2)); number2.start(); number3 = new Thread(new NumberPrinter(3)); number3.start(); thread.pdf Pag. 5/10 Cozzetto

number4 = new Thread(new NumberPrinter(4)); number4.start(); number5 = new Thread(new NumberPrinter(5)); number5.start(); // main() // Numbers class In questo caso a ciascun thread viene passato come argomento del costruttore un oggetto di tipo Runnable. Priorità dei thread Il metodo setpriority(int priority) consente di cambiare la priorità di un thread (l'argomento di setpriority deve essere un intero compreso tra Thread.MIN_PRIORITY =1 e Thread.MAX_PRIORITY = 10), mentre il metodo getpriority() restituisce un numero intero che rappresenta la priorità del thread. Di solito i thread con priorità più alta, vengono eseguiti per un tempo più lungo rispetto ai thread con bassa priorità. Morte per fame Un thread con priorità alta che teoricamente non rilascia mai la CPU potrebbe non consentire mai l'esecuzione a un thread con bassa priorità. In questo caso si parla di starvation ovvero morte per fame del thread con bassa priorità. Vi deve essere veramente un buon motivo per cambiare la priorità di un thread! Si rimanda a percorsi di studio superiori. Metodo sleep() Il metodo sleep(int milliseconds) consente al thread corrente (è infatti un metodo statico) di sospendere per un certo numero di millisecondi l'esecuzione del thread. Il thread sospeso va nello stato di sleeping e allo scadere dell'intervallo temporale, il thread non torna nello stato di esecuzione ma bensì va nello stato di Runnable (indicato con Ready nella fig. 5), cioè è elegibile per l'esecuzione. Il metodo sleep lancia una InterruptedException. Come esempio, sostituiamo al metodo run() della classe NumberPrinter questo nuovo metodo: public void run() { for (int k=0; k < 10; k++) { try { // scegliamo un numero casuale compreso tra 0s e 1s Thread.sleep((long)(Math.random() * 1000)); catch (InterruptedException e) { System.out.println(e.getMessage()); System.out.print(num); // for // run() L'esecuzione potrebbe essere simile alla seguente: 14522314532143154232152423541243235415523113435451 Ogni thread è forzato a dormire per un certo numero (casuale) di millisecondi compreso tra 0 e 1 e, quando un thread dorme, ciò consente a un altro thread di essere eseguito. L'output come si può vedere riflette la casualità dell'attesa. Metodo yield() Anche il metodo statico yield() causa il rilascio del thread ma, rispetto a sleep(), il thread potrebbe tornare immediatamente ad essere eseguito (va cioè nello stato Runnable per cui lo scheduler potrebbe riassegnarlo alla CPU). Metodo isalive() Il metodo (non stativo) isalive() restituisce true se un thread è stato fatto partire ma non ha ancora completato il suo ciclo di vita (isalive non ci dice però se il thread è in esecuzione). thread.pdf Pag. 6/10 Cozzetto

Metodo join() Se t è un thread, il richiamo del metodo join() (non statico) su t sospende l'esecuzione del thread corrente (quello che ha chiamato il join()) e consente al thread t di terminare la sua esecuzione. Non appena il thread t ha terminato, il thread corrente riprende la sua esecuzione (in altre parole, il thread corrente si sospende e attende la terminazione del thread t). Altri metodi deprecati Esistono altri metodi, come stop(), suspend(), resume() ecc che si potrebbero usare per controllare i thread ma essi sono deprecati perchè agiscono troppo bruscamente sui thread portando, sotto determinate condizioni, il programma a bloccarsi. Stati di un thread Un thread normalmente evolve nel corso della sua vita passando attraverso diversi stati di cui in parte abbiamo già parlato. Ricordiamo qui che i metodi wait(), notify() e notifyall() si usano solo nelle cosiddette regioni critiche (metodi contrassegnati dalla parola chiave synchronized). Ne parleremo poco più avanti. Fig. 5 Ciclo di vita di un thread Interferenza tra i thread Consideriamo la seguente semplice classe: class Counter { private int c = 0; public void increment() { c++; public void decrement() { c--; public int value() { return c; // fine Counter Il metodo increment come si può vedere provvederà a incrementare una variabile intera c, mentre l'altro provvederà a decrementarla. Tuttavia se un oggetto di tipo Counter è referenziato ad esempio da due thread, (il primo che incrementa e l'altro che decrementa), la sequenza di esecuzione di un thread può interferire con la sequenza di esecuzione dell'altro thread, dando luogo a risultati non congruenti. thread.pdf Pag. 7/10 Cozzetto

Chiariamo con un esempio. Immaginiamo che la sequenza relativa all'incremento di c sia scomponibile nei seguenti passi in linguaggio macchina: 1. Leggi il valore corrente di c e ponilo in un registro 2. Incrementa il valore del registro di 1 3. Copia il valore del registro in c In maniera analoga possiamo immaginare la sequenza relativa al decremento (cambia solo lo step 2). Supponiamo ora che il thread A invochi l'incremento di c mentre allo stesso tempo il thread B invochi il decremento di c. Se inizialmente c=0, una possibile sequenza dei passi potrebbe essere: 1. Thread A: Recupera c. 2. Thread B: Recupera c. 3. Thread A: Incrementa il valore appena trovato; risultato 1. 4. Thread B: Decrementa il valore appena recuperato; risultato -1. 5. Thread A: Memorizza il risultato in c; c ora vale 1. 6. Thread B: Memorizza il risultato in c; c è ora -1. Come si vede, il risultato del thread A è andato perso, sovrascritto dal risultato del thread B. In alcune circostanze, poteva andare perso invece il risultato del thread B, in altre ancora potevano non esserci errori. Dal momento che il comportamento dei thread non è prevedibile a priori, risulta difficile anche l'attività di debugging e di fix degli errori. Il problema si verifica perchè abbiamo consentito ai due thread di interferire tra di loro. Una soluzione al problema consiste nel considerare indivisibili o atomiche le sequenze dei passi da 1 a 3 compiute da ciascun thread. Si parla in tal caso di sezioni critiche. Un buon modello che spiega come impedire queste situazioni è il concetto di Monitor (o di Lock, in letteratura esistono altri nomi e altri concetti collegati a questo). Immaginiamo il Monitor come una scatolina con una chiave posta nelle vicinanze entro la quale si trova un solo thread alla volta. Se due thread devono accedere (su uno stesso oggetto) a una sezione critica, allora il primo thread che riesce ad entrare nel Monitor chiude a chiave la scatolina dall'interno (nel frattempo gli altri thread che desiderano accedere al Monitor aspettano il loro turno), esegue i passi della sezione critica e quando ha terminato, riapre la scatolina, ridepositando la chiave nei pressi del Monitor. Si dice allora che i passi della sezione critica vengono eseguiti in Mutua Esclusione. I thread come si è potuto vedere sincronizzano i loro comportamenti evitando possibili incongruenze. Fig. 6 Il Lock su un oggetto In Java, le sezioni critiche devono essere marcate con la parola riservata synchronized. Segue il codice completo del problema posto precedentemente. thread.pdf Pag. 8/10 Cozzetto

/* * Contatore.java package mieclassi; * @author maurizio public class Contatore { private int c = 0; public synchronized void incrementa() { c++; System.out.println("Contatore="+c+" (Eseguito dal thread "+Thread.currentThread().getName()+")"); public synchronized void decrementa() { c--; System.out.println("Contatore="+c+" (Eseguito dal thread "+Thread.currentThread().getName()+")"); // fine classe Contatore /* * IncrementaThread.java package mieclassi; * * @author maurizio public class IncrementaThread extends Thread { private Contatore cont; public IncrementaThread(Contatore cont) { this.cont = cont; @Override public void run() { for (int i=0; i<10; i++) { cont.incrementa(); try { Thread.sleep(100); catch (InterruptedException e) { // // fine classe IncrementaThread thread.pdf Pag. 9/10 Cozzetto

/* * IncrementaThread.java package mieclassi; * * @author maurizio public class DecrementaThread extends Thread { private Contatore cont; public DecrementaThread(Contatore cont) { this.cont = cont; @Override public void run() { for (int i=0; i<10; i++) { cont.decrementa(); try { Thread.sleep(100); catch (InterruptedException e) { // // fine classe DecrementaThread /* * Main.java package mieclassi; * * @author maurizio public class Main { * @param args the command line arguments public static void main(string[] args) { Contatore cont = new Contatore(); Thread a = new IncrementaThread(cont); a.setname("a"); a.start(); Thread b = new DecrementaThread(cont); b.setname("b"); b.start(); // fine classe Main thread.pdf Pag. 10/10 Cozzetto