Thread in Java CAPITOLO 18. 18.1 Richiami di gestione di processi



Documenti analoghi
Java Virtual Machine

Multithreading in Java. Fondamenti di Sistemi Informativi

GESTIONE DEI PROCESSI

Java threads (2) Programmazione Concorrente

Realizzazione di Politiche di Gestione delle Risorse: i Semafori Privati

COGNOME.NOME. Matricola

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:

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

Telematica II 17. Esercitazione/Laboratorio 6

Monitor. Introduzione. Struttura di un TDA Monitor

Pronto Esecuzione Attesa Terminazione

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

Il Sistema Operativo

Sistemi Operativi (modulo di Informatica II) I processi

Gestione Turni. Introduzione

FONDAMENTI di INFORMATICA L. Mezzalira

12 - Introduzione alla Programmazione Orientata agli Oggetti (Object Oriented Programming OOP)

Sistema Operativo. Fondamenti di Informatica 1. Il Sistema Operativo

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

Il costrutto monitor [Hoare 74]

Funzioni in C. Violetta Lonati

La gestione di un calcolatore. Sistemi Operativi primo modulo Introduzione. Sistema operativo (2) Sistema operativo (1)

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

Modulo 4: Ereditarietà, interfacce e clonazione

Inizializzazione, Assegnamento e Distruzione di Classi

2.0 Gli archivi. 2.1 Inserire gli archivi. 2.2 Archivio Clienti, Fornitori, Materiali, Noleggi ed Altri Costi. Impresa Edile Guida all uso

ARCHITETTURA DI RETE FOLEGNANI ANDREA

Esercitazione n 4. Obiettivi

Programmazione concorrente in Java

I Canvas. import java.awt.*; import javax.swing.*; public class Graf{ public Graf () { JFrame f = new JFrame("Finestra"); // crea frame invisibile

Synchronized (ancora)

Gestione Risorse Umane Web

Oggetti Lezione 3. aspetti generali e definizione di classi I

Gestione dei thread in Java LSO 2008

Finalità della soluzione Schema generale e modalità d integrazione Gestione centralizzata in TeamPortal... 6

La struttura dati ad albero binario

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

Java: Compilatore e Interprete

Il problema del produttore e del consumatore. Cooperazione tra processi

Sequence Diagram e Collaboration Diagram

Indice generale. OOA Analisi Orientata agli Oggetti. Introduzione. Analisi

Linguaggi Corso M-Z - Laurea in Ingegneria Informatica A.A Esercitazione. Programmazione Object Oriented in Java

Un esercizio d esame. Flavio De Paoli

Corso di Informatica

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

Java Applet. Linguaggi Corso M-Z - Laurea in Ingegneria Informatica A.A

CP Customer Portal. Sistema di gestione ticket unificato

E possibile modificare la lingua dei testi dell interfaccia utente, se in inglese o in italiano, dal menu [Tools

Programmazione Orientata agli Oggetti in Linguaggio Java

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

Manuale Amministratore Legalmail Enterprise. Manuale ad uso degli Amministratori del Servizio Legalmail Enterprise

Esercizio 1: trading on-line

Scheduling della CPU. Sistemi multiprocessori e real time Metodi di valutazione Esempi: Solaris 2 Windows 2000 Linux

19. Introduzione al multi-threading

Consiglio regionale della Toscana. Regole per il corretto funzionamento della posta elettronica

Le basi della grafica in Java. Prof. Francesco Accarino IIS Altiero Spinelli via Leopardi 132 Sesto san Giovanni

Architettura MVC-2: i JavaBeans

Appunti sulla Macchina di Turing. Macchina di Turing

Gestione della memoria centrale

Il Sistema Operativo (1)

Object Oriented Programming

Coordinazione Distribuita

10 - Programmare con gli Array

13 - Gestione della Memoria nella Programmazione Orientata agli Oggetti

Archivio CD. Fondamenti di Programmazione

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

GUIDA ALLA PROGRAMMAZIONE GRAFICA IN C

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

MODELLISTICA DI IMPIANTI E SISTEMI 2

Sistemi Operativi MECCANISMI E POLITICHE DI PROTEZIONE. D. Talia - UNICAL. Sistemi Operativi 13.1

MECCANISMI E POLITICHE DI PROTEZIONE 13.1

Regione Toscana. ARPA Fonte Dati. Manuale Amministratore. L. Folchi (TAI) Redatto da

EXPLOit Content Management Data Base per documenti SGML/XML

Corso di Informatica

STRUTTURE DEI SISTEMI DI CALCOLO

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

Introduzione alla programmazione in C

Gestione Rapporti (Calcolo Aree)

Progettazione : Design Pattern Creazionali

FPf per Windows 3.1. Guida all uso

GESTIONE INFORMATICA DEI DATI AZIENDALI

Airone Gestione Rifiuti Funzioni di Esportazione e Importazione

MANUALE MOODLE STUDENTI. Accesso al Materiale Didattico

FIRESHOP.NET. Gestione completa degli ordini e degli impegni. Rev

11/02/2015 MANUALE DI INSTALLAZIONE DELL APPLICAZIONE DESKTOP TELEMATICO VERSIONE 1.0

SISTEMI OPERATIVI. Deadlock (blocco critico) Domande di verifica. Luca Orrù Centro Multimediale Montiferru 04/06/2007

Light CRM. Documento Tecnico. Descrizione delle funzionalità del servizio

Database. Si ringrazia Marco Bertini per le slides

Soluzione dell esercizio del 2 Febbraio 2004

Il descrittore di processo (PCB)

Automazione Industriale (scheduling+mms) scheduling+mms.

Sistemi Operativi. Processi GESTIONE DEI PROCESSI. Concetto di Processo. Scheduling di Processi. Operazioni su Processi. Processi Cooperanti

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

CAPITOLO 7 - SCAMBIO DI MESSAGGI

15J0460A300 SUNWAY CONNECT MANUALE UTENTE

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

13. Chain of Responsibility

Comune di San Martino Buon Albergo

Libero Emergency PC. Sommario

COLLI. Gestione dei Colli di Spedizione. Release 5.20 Manuale Operativo

Transcript:

CAPITOLO 18 Thread in Java Usualmente l esecuzione di un programma è sostenuta da un processo. Nella terminologia Java i processi sono denominati thread. Un thread è dunque costituito da un flusso di esecuzione di istruzioni. In molti casi è opportuno disporre di architetture software di tipo multi-thread, sia perché il calcolatore utilizzato può disporre di hardware dotato di più processori, ed essere pertanto in grado di effettuare computazioni indipendenti, sia perché la risoluzione di un particolare problema può essere organizzata in più sottoproblemi paralleli parzialmente separabili e autonomi. Si sottolinea in particolare che nella macchina virtuale di Java, nel seguito denominata JVM, la gestione dei frame costituenti l interfaccia grafica e delle operazioni che in essi hanno luogo è demandata a thread separati, uno per ciascun frame. 18.1 Richiami di gestione di processi Architetture software basate sull esistenza di processi indipendenti sono attuabili in ogni calcolatore purché il suo sistema operativo lo consenta. Ciò avviene in particolare con macchine dotate di sistemi operativi multitasking. In questi sistemi più processi possono coesistere ed interagire tra di loro scambiandosi reciprocamente informazioni. Dal punto di vista del sistema operativo non vi è alcuna differenza sostanziale tra il trattamento dei processi utente e quello dei processi di sistema. Va tuttavia rilevato che la gestione dei processi nei sistemi multitasking è piuttosto delicata, essendo mirata ad ottimizzare le risorse del sistema nella sua globalità. Ciò comporta, in particolare, che la computazione effettuata dai processi attivi nel calcolatore possa essere temporaneamente sospesa o subire modificazioni nella priorità di esecuzione, sia in base alle operazioni definite nei processi medesimi sia in forza di specifiche politiche di gestione previste dal sistema operativo. Con riferimento all ultima osservazione svolta, si consideri che in un sistema multitasking ogni processo può trovarsi sempre in uno solo di tre stati: esecuzione, attesa oppure pronto. In ogni istante un solo processo è in esecuzione, mentre più processi possono trovarsi accodati

18 CAPITOLO 18 Figura 18.1 Grafo di transizione tra gli stati di un processo. negli altri stati. Tipicamente il passaggio di un processo da uno stato ad un altro è conseguenza di richieste di operazioni di I/O oppure di completamento di quest ultime. È peraltro possibile che un processo si sospenda autonomamente o venga sospeso al fine di sincronizzarlo con altro processo concorrente. La Figura 18.1 evidenzia il grafo di transizione per gli stati di un processo. Appena creato, il processo è collocato nella coda dei processi pronti, dalla quale è fatto passare in esecuzione dal sistema operativo. Dallo stato di esecuzione il processo può: (a) terminare, venendo rimosso dalla lista dei processi; (b) venire sospeso a seguito di una richiesta di I/O oppure di accesso ad altra risorsa e venire inserito nella coda dei processi in attesa sino a quando tale risorsa non sarà disponibile; (c) venire sospeso su richiesta di altro processo o del sistema operativo ed essere trasferito nella coda dei processi pronti sino a quando non verrà richiesta la sua riattivazione. Dalla coda di attesa il processo viene rimosso in seguito al completamento dell operazione di I/O oppure per essersi liberata la risorsa richiesta, venendo inserito nella lista dei processi pronti per continuare l esecuzione. Nella gestione d insieme dei processi vi sono alcuni aspetti che si presentano critici. Uno di essi, di particolare interesse per gli argomenti trattati nel testo, riguarda la sincronizzazione per l interscambio di dati e la conseguente possibilità di mutua sospensione e successiva riattivazione. Le problematiche connesse non verranno tuttavia trattate nella loro generalità, ma facendo esplicito riferimento all architettura della macchina virtuale di Java. 18.2 I thread di Java In Java i processi vengono chiamati thread e costituiscono dei flussi di esecuzione. Si è già fatto notare che ogni frame è associato ad un proprio thread, il quale è responsabile delle computazioni operate a livello di finestra. Più in generale va detto che la JVM è basata su un architettura multi-thread per la cui gestione è fornita la classe Thread afferente al package java.lang. Il modello di processi usato dalla classe è improntato agli assunti sotto indicati. Ogni oggetto thread è depositario di un identificatore, non necessariamente univoco, il cui uso è limitato ad eventuali operazioni di debugging dell applicazione. Usualmente tale identificatore non è di alcuna utilità nelle applicazioni.

THREAD IN JAVA 19 Ogni thread afferisce ad un thread group. L insieme dei gruppi di thread presenti nella macchina Java costituisce una struttura ad albero, con radice data dal system thread group, della cui gestione è responsabile il sistema. Ogni thread possiede una priorità di esecuzione, che è costituita da un numero intero compreso tra 1 e 10 assegnabile al thread in fase di programmazione. Un thread può assumere la connotazione di demone. I demoni peraltro costituiscono processi particolari, le proprietà funzionali dei quali non sono qui trattate in quanto esulano dagli scopi del testo. Per quanto concerne gli stati dei thread la JVM fa proprio il seguente modello: un thread è definito attivo nel caso in cui sia stata lanciata la sua esecuzione e questa non si è ancora completata; è definito inattivo in caso contrario; un thread è definito sospeso nel caso in cui la sua esecuzione sia stata temporaneamente interrotta; lo stato di sospensione potrà essere successivamente eliminato ripristinandone l esecuzione. Tutte le trasformazioni di stato di un thread sono effettuate a seguito dell attivazione di opportuni metodi della classe Thread. Prima di analizzare in dettaglio tali metodi sembra tuttavia opportuno puntualizzare quali siano le più significative funzionalità da essi espletate sui thread: allocazione e inizializzazione: coincide con la creazione dell oggetto della classe Thread che dovrà contenere il modello computazionale del thread e con la definizione del contesto in cui dovrà essere eseguito il processo; in tale fase il thread è inattivo; attivazione: causa l inizio della computazione di pertinenza del thread; il thread diviene attivo; sospensione: causa una (temporanea) interruzione dell esecuzione del thread; il thread passa nello stato di thread attivo ma sospeso; ripristino: causa la riattivazione dell esecuzione del thread sospeso; il thread ritorna nello stato attivo; terminazione forzata: causa una fine anomala nell esecuzione del thread; il thread ritorna nello stato inattivo; distruzione: causa la deallocazione del thread, senza che ciò comporti necessariamente il rilascio delle risorse da esso impegnate. I metodi forniti dalla classe Thread per fronteggiare le operazioni indicate permettono di gestire nel tempo l evoluzione dei singoli thread nella macchina virtuale di Java, ma non intervengono in alcun modo sul modello computazionale da questi sostenuto. Al fine di permettere al programmatore di definire il codice dei propri thread il linguaggio mette a disposizione il metodo run(), definito nell interfaccia Runnable e implementato dalla classe Thread. Tale metodo viene richiamato in fase di attivazione del thread ed è pertanto destinato ad essere riscritto, nelle sottoclassi d applicazione derivate da Thread, con il codice che è proprio del processo computazionale che si intende associare a quest ultime. Nel seguito sono riportati i metodi della classe Thread che rivestono maggior interesse per le applicazioni sviluppate in questo testo.

20 CAPITOLO 18 Il primo gruppo di metodi è destinato alla creazione dei thread e alla descrizione del modello computazionale da essi realizzato. Si tratta di metodi di largo impiego da usare ogniqualvolta un thread debba essere implementato. Thread Thread() Thread (Runnable target) void run() Il costruttore Thread() alloca un nuovo thread assegnandogli un identificatore del tipo Thread-n, in cui n è un numero naturale progressivo: tipicamente Thread-0 rappresenta il thread associato al metodo main(). Usato nella sua prima forma, il costruttore è riferito a thread istanziati da sottoclassi della classe Thread, mentre nella seconda forma è riferito a thread costituiti da istanze di classi che implementano l interfaccia Runnable. Il metodo run() permette di specificare il modello computazionale del thread. Se il thread è costruito a partire da una classe implementante l interfaccia Runnable, il metodo run() della classe Thread richiama l omonimo metodo dell oggetto Runnable. Se il thread si origina da una sottoclasse di Thread, tale sottoclasse deve riscrivere il metodo run() della classe Thread. Il gruppo di metodi che seguono permette di definire le priorità e la natura dei thread. Thread void setpriority (int newpriority) int getpriority() void setdaemon (boolean on) boolean isdaemon() Il metodo setpriority() permette di modificare la priorità di un thread. Il valore di priorità assegnato coincide con il minore tra il valore del parametro newpriority e il valore di priorità del thread group a cui il thread appartiene. Viceversa, il metodo get- Priority() restituisce il valore di priorità del thread. Il metodo setdaemon() permette di definire un thread come demone o meno. Il metodo deve essere invocato prima dell attivazione del thread. La JVM termina la sua attività quando i soli thread attivi sono costituiti da demoni. Il metodo isdaemon() permette di controllare se il thread a cui è inoltrato l omonimo messaggio è un thread demone o meno. Quattro metodi erano destinati nella prima versione di Java alla realizzazione delle principali operazioni di cambiamento di stato dei thread che sono state analizzate in precedenza.

THREAD IN JAVA 21 Thread void start() void suspend() void resume() void stop() boolean isalive() Il metodo start() induce l esecuzione del thread portandolo nello stato di thread attivo. La macchina virtuale Java richiama il metodo run() del thread. Il metodo suspend() causa la sospensione del thread portandolo nello stato di thread sospeso. L esecuzione del thread potrà essere successivamente ripristinata con il metodo resume(). Il metodo può essere interessato da un eccezione di tipo SecurityException. Il metodo resume() ripristina l esecuzione del thread qualora esso si trovi nello stato di thread attivo ma sospeso. Il metodo può essere interessato da un eccezione di tipo SecurityException. Il metodo stop() forza il thread ad interrompere in modo anomalo la propria esecuzione sollevando un eccezione ThreadDeath. L eccezione deve essere fatta rimbalzare al metodo chiamante perché il thread termini correttamente. Lo stato di attività dei thread è verificabile ricorrendo al metodo isalive(); il metodo controlla se il thread è attivo, ossia se su di esso è stato invocato il metodo start() e il thread non ha ancora raggiunto lo stato di terminazione. I metodi suspend(), resume() e stop() sono stati peraltro rimossi (dichiarati deprecated) nelle versioni successive del linguaggio, in quanto rivelatisi possibili cause di situazioni di deadlock. La sospensione di un thread o la sua terminazione vanno quindi gestite esclusivamente con i metodi del gruppo sotto indicato. Thread static Thread currentthread() static void yield() static void sleep (long millis) void join() Il metodo currentthread() restituisce un riferimento al thread correntemente in esecuzione. È principalmente utilizzato per acquisire informazioni sul thread. Il metodo yield() causa la temporanea sospensione del thread corrente, permettendo ad altri thread in attesa di divenire attivi. Il metodo sleep() causa la sospensione del thread per il numero di millisecondi indicato dal parametro millis. Il metodo join() induce il completamento del thread corrente prima di rendere possibile l attivazione del thread successivo.

22 CAPITOLO 18 Due metodi sono infine destinati alle operazioni di deallocazione dei thread. Thread void destroy() private void exit() Il metodo destroy() causa la deallocazione del thread, ma non delle risorse da esso impegnate. Il metodo exit() è usato dal sistema per permettere al thread di effettuare la deallocazione delle proprie risorse. 18.3 Creazione di thread La creazione di un thread può essere espletata in Java seguendo due metodologie: per ereditarietà dalla classe Thread; implementando l interfaccia Runnable. La scelta di una soluzione o dell altra è dettata da preferenze personali e dalle caratteristiche dell applicazione. 18.3.1 Creazione di sottoclassi Thread Qualora si voglia realizzare un thread sfruttando l omonima classe, è necessario effettuare le seguenti operazioni: costruire una sottoclasse della classe Thread; sovrascrivere il metodo run(). Il metodo run() della sottoclasse fornisce in tal modo il corpo del nuovo thread. Una volta generata la sottoclasse, è possibile: creare un thread per istanza della sottoclasse di pertinenza; eseguire il thread per chiamata del metodo start(), il quale invoca a sua volta il metodo run(). 18.3.2 Implementazione dell interfaccia Runnable Questa seconda soluzione è preferibile nel caso in cui si vogliano creare thread evitando situazioni che potrebbero condurre a forme di ereditarietà multipla, non ammessa da Java. Le operazioni necessarie sono le seguenti: costruzione di una classe di implementazione per l interfaccia Runnable; sovrascrittura del metodo run(). Anche in questa situazione il metodo run() della classe fornisce il corpo del nuovo thread. Ciò fatto, un thread viene creato: generando un istanza della classe che implementa Runnable; passando tale istanza come parametro del costruttore della classe Thread. Il thread è eseguito per chiamata del metodo start().

THREAD IN JAVA 23 18.4 Sincronizzazione dei metodi In un sistema multi-thread coesistono in generale molti thread attivi, i quali costituiscono processi in grado di evolvere autonomamente e capaci di inoltrarsi reciprocamente messaggi. La ricezione di un messaggio da parte di un thread comporta l attivazione del metodo corrispondente. Sono dunque possibili situazioni in cui potrebbe essere richiesta la contemporanea esecuzione di due o più metodi di uno stesso thread: uno di essi eseguito per esigenza del thread depositario del metodo, gli altri perché invocati da messaggi inoltrati da thread diversi. Tale situazione può dimostrarsi estremamente critica, dal momento che la richiesta di esecuzione di metodi da thread esterni potrebbe interferire con l esecuzione della computazione del thread destinatario dei messaggi. Se infatti esistono situazioni in cui l alternata esecuzione dei metodi in questione da parte della JVM può essere opportuna, esistono sicuramente situazioni in cui tale alternanza va impedita. Il problema evidenziato è risolvibile fornendo il linguaggio di opportuni dispositivi per la sincronizzazione dei metodi. Sincronizzare due o più metodi, in sostanza, significa garantire che l esecuzione di ciascuno di essi non venga interrotta dall esecuzione dei rimanenti, neppure in caso di temporanea sospensione del thread a cui i metodi appartengono. In Java la sincronizzazione dei metodi è ottenuta mediante l uso del modificatore synchronized. Qualora i metodi di un thread siano dichiarati sincronizzati, l attivazione di uno di essi blocca le richieste di esecuzione dei metodi rimanenti quando inoltrate alla stessa istanza di thread. La situazione è descritta in Figura 18.2, in cui il thread thd1 è istanziato dalla classe ClasseDiThread e attivato con l esecuzione del metodo m1(). Il thread thd2, che inoltra il messaggio thd1.m2(), dovrà attendere il completamento di m1() per ottenere l esecuzione del metodo m2(). Quanto sopra asserito si applica ai metodi d istanza. Il modificatore synchronized può tuttavia essere applicato anche ai metodi di classe. Qualora due o più metodi statici sincronizzati di una stessa classe vengano chiamati da thread diversi, le loro esecuzioni subiscono un processo di serializzazione in modo che nessuno di essi possa essere interrotto dall esecuzione dei rimanenti. Figura 18.2 Sincronizzazione di metodi d istanza.

24 CAPITOLO 18 18.4.1 Un esempio di sincronizzazione di metodi La classe Frame_xThread (Scheda 18.4a) fornisce un esempio di sincronizzazione di metodi secondo un modello produttore-consumatore. Si supponga di disporre di otto dischi blu posizionati nella regione di sinistra di un frame, come indicato in Figura 18.3 (a), e di voler raggiungere la configurazione di otto dischi rossi riportata in 18.3 (b). La trasformazione deve avvenire per produzione di dischi blu da rendere disponibili per la trasformazione in corrispondenti dischi rossi. Figura 18.3 Configurazioni iniziale e finale di un frame. Il processo che sottende la trasformazione delle configurazioni è basato su due metodi principali: il metodo produci() e il metodo consuma(). L esempio ha la funzione di mostrare la differenza tra le configurazioni intermedie, necessarie al raggiungimento di quella finale, che possono essere attraversate a seconda che i due metodi citati siano dichiarati synchronized o meno. Le due regioni di tracciatura in cui è suddivisa la finestra grafica sono rispettivamente denominate rgnprod e rgncons, a rappresentazione dello stato del thread causato dai metodi produci() e consuma(): il metodo produci()opera sulla regione rgnprod: esso riduce progressivamente di un unità il numero di dischi presenti nella regione, richiede la tracciatura della nuova configurazione della stessa e si sospende per un secondo; il metodo consuma() opera in modo analogo sulla regione rgncons, incrementando progressivamente di un unità il numero di dischi della regione, richiedendo la tracciatura della nuova configurazione raggiunta e sospendendosi per uno stesso periodo temporale. La sospensione del thread è inserita sia per permettere all osservatore di seguire l evoluzione del processo di trasformazione nella distribuzione dei dischi nelle due regioni, sia per rendere palese il diverso funzionamento dell applicazione quando i metodi produci() e consuma() sono entrambi dichiarati sincronizzati rispetto a quando non lo sono.

THREAD IN JAVA 25 La trasformazione complessiva delle configurazioni di dischi nelle due regioni di tracciatura del frame proviene dalla coordinata esecuzione di entrambi i metodi produci() e consuma(). A tale scopo è necessario che l applicazione a ciò preposta generi un thread, lo si denomini th1, da attivare con il comando th1.start() per l esecuzione del metodo consuma() e in seguito invochi il metodo produci() dello stesso thread. Lo schema computazionale complessivamente ottenuto è analogo a quello riportato in Figura 18.2: il thread thd1 con il metodo m1() è sostituito dal thread th1 e dal metodo consuma(), mentre il thread esterno thd2 inoltrante il messaggio thd1.m2() è sostituito dal thread contenente il programma main() dell applicazione e dal messaggio produci(). Le configurazioni ottenute lanciando l applicazione sono diverse a seconda che i metodi produci() e consuma() siano o meno dichiarati entrambi sincronizzati. Nel caso in cui entrambi i metodi siano dichiarati synchronized, il metodo produci(), che è in esecuzione appartenendo al thread del metodo main(), non può essere interrotto neppure nei suoi periodi di sospensione dal metodo consuma() del thread th1. Pertanto inizialmente si assisterà ad una successiva rimozione di dischi dalla regione rgnprod, come riportato in Figura 18.4, sino al raggiungimento della configurazione di Figura 18.5. Soltanto a questo punto potrà essere attivato il metodo consuma() del thread th1, il quale procederà ad inserire dischi nella regione rgncons, generando la successione di configurazioni di Figura 18.6. Figura 18.4 Configurazioni parziali generate dal metodo produci().

26 CAPITOLO 18 Figura 18.5 Configurazione finale generata dal metodo produci(). Figura 18.6 Configurazioni generate dal metodo consuma(). Qualora uno dei due metodi non sia dichiarato synchronized, produci() e consuma() procederanno autonomamente, alternandosi nella generazione di configurazioni parziali. Il risultato sarà di assistere ad una progressiva eliminazione di dischi blu dalla regione rgnprod e al contestuale inserimento di dischi rossi in rgncons.

THREAD IN JAVA 27 Figura 18.7 Configurazioni generate dai metodi produci() e consuma(). Scheda 18.4a - class Argomenti class Frame_xThread Sincronizzazione di metodi su una stessa istanza di thread. Modello implementato: produttore-consumatore. import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import java.util.*; import javax.swing.*;

28 CAPITOLO 18 public class Frame_xThread extends JFrame implements Runnable { private final int ncmax = 8; private final int r2 = 20; private PanThread rgnprod, rgncons; private int cinit_prod = ncmax, cinit_cons = 0; private int cbasex = 20; private int cbasey = 20; public Frame_xThread() { setdefaultcloseoperation (JFrame.EXIT_ON_CLOSE); Container cnt = getcontentpane(); cnt.setbackground (Color.white); cnt.setlayout (new BorderLayout()); settitle ( Produttore - Consumatore ); setbounds (30, 30, 700, 200); rgnprod = new PanThread (cinit_prod); rgnprod.setsize (340, 200); rgnprod.setbackground (Color.yellow); rgnprod.setforeground (Color.blue); cnt.add (rgnprod, West ); rgncons = new PanThread (cinit_cons); rgncons.setsize (340, 200); rgncons.setbackground (Color.cyan); rgncons.setforeground (Color.red); cnt.add (rgncons, East ); Panel controlli = new JPanel(); controlli.setbackground (Color.lightGray); cnt.add (controlli, South ); setvisible (true true); class PanThread extends Canvas { public int ncerchi; public PanThread (int ncerchi) { super(); this.ncerchi = ncerchi; public void paint (Graphics g) { int cx = cbasex; int cy = cbasey; Graphics2D g2 = (Graphics2D) g; for (int c = 0; c < ncerchi; c++) { cx = cx + 2*r2; g2.fill (new Ellipse2D.Duoble (cx, cy, r2, r2)); for (int c = ncerchi + 1; c < ncmax+1; c++) { cx = cx + 2*r2; g2.draw (new Ellipse2D.Duoble (cx, cy, r2, r2));

THREAD IN JAVA 29 La classe Frame_xThread dispone di un frame suddiviso in due regioni. Nella regione di sinistra sono collocati otto dischi blu. Il programma realizzato dalla classe comporta l eliminazione dei dischi blu dalla regione sinistra con corrispondente generazione di otto dischi rossi nella regione destra. Il processo che sottende la trasformazione è basato su due metodi principali: il metodo produci() e il metodo consuma(). La trasformazione deve dunque avvenire per produzione di dischi blu da rendere disponibili per la consumazione (trasformazione) in corrispondenti dischi rossi. La classe è realizzata in forma mista, sfruttando sia classi dell architettura AWT sia classi JFC/Swing. La forma complessiva del frame prevede due regioni di tracciatura, istanziate dalla classe PanThread derivata da Canvas, e un sottostante pannello vuoto di classe JPanel inserito per possibili usi successivi. Le due regioni di tracciatura, denominate rgnprod e rgncons, sono destinate a visualizzare i dischi blu e rossi afferenti alle sottofinestre sinistra e destra del frame a mano a mano che il processo evolve. La regione rgnprod è inizializzata con ncmax = 8 dischi blu, mentre la regione rgncons è priva di dischi: le variabili cinit_prod e cinit_cons stabiliscono lo stato iniziale delle due regioni. I colori dei dischi provengono dalla definizione dei colori di foreground delle rispettive regioni, mentre la costante r2 ne stabilisce il diametro. Le variabili cbasex e cbasey definiscono le posizioni dei punti iniziali a partire dai quali saranno collocati i dischi nelle due regioni di tracciatura. Dichiarazione delle variabili private final int ncmax = 8; private final int r2 = 20; private PanThread rgnprod, rgncons; private int cinit_prod = ncmax, cinit_cons = 0; private int cbasex = 20; private int cbasey = 20; Dopo la creazione della finestra grafica, derivata dalla classe JFrame nel modo consueto al contesto JFC/Swing e basata su un modello di distribuzione dei componenti di tipo BorderLayout, la classe crea le regioni rgnprod e rgncons istanziandole dalla classe PanThread e dotandole dei dischi di propria pertinenza. In questa fase il metodo setbackground() assegna alla regione rgnprod uno sfondo giallo e il metodo setforeground() stabilisce per essa il colore di tracciatura blu. I corrispondenti colori della regione rgncons sono il ciano e il rosso. Le due regioni vengono quindi inserite dal metodo add() nelle parti West e East del layout del contenitore cnt del frame. Generazione del frame public Frame_xThread() { rgnprod = new PanThread (cinit_prod); rgnprod.setsize (340, 200); rgnprod.setbackground (Color.yellow); rgnprod.setforeground (Color.blue); cnt.add (rgnprod, West ); rgncons = new PanThread (cinit_cons); rgncons.setsize (340, 200);

30 CAPITOLO 18 rgncons.setbackground (Color.cyan); rgncons.setforeground (Color.red); cnt.add (rgncons, East ); Successivamente è creato il pannello controlli, che viene dotato di sfondo grigio e allocato nella parte South del contenitore cnt. L ultima operazione di Frame_xThread() consiste nel rendere visibile il frame con il metodo setvisible(). Completamento del frame public Frame_xThread() { Panel controlli = new JPanel(); controlli.setbackground (Color.lightGray); cnt.add (controlli, South ); setvisible (true true); Il processo di visualizzazione dei dischi nelle due regioni di tracciatura rgnprod e rgncons è realizzato dalla classe interna PanThread. Il costruttore della classe trascrive nella variabile d istanza ncerchi il numero di dischi inizialmente presenti nella regione. Tale valore verrà modificato durante l esecuzione del programma a mano a mano che i dischi verranno trasferiti dalla regione rgnprod alla regione rgncons. Costruttore della classe di visualizzazione public PanThread (int ncerchi) { super(); this.ncerchi = ncerchi; Il metodo paint() utilizza il valore di ncerchi per tracciare a campitura piena, con il metodo fill(), i dischi presenti nella regione su cui è attivato e per disegnare con il metodo draw() il profilo dei dischi mancanti. In tale contesto i valori di cbasex e cbasey definiscono i punti di riferimento iniziali del processo di tracciatura, mentre r2 stabilisce l ampiezza del rettangolo di contenimento dei dischi. Visualizzazione di regione public void paint (Graphics g) { int cx = cbasex; int cy = cbasey; Graphics2D g2 = (Graphics2D) g; for (int c = 0; c < ncerchi; c++) { cx = cx + 2*r2; g2.fill (new Ellipse2D.Duoble (cx, cy, r2, r2)); for (int c = ncerchi + 1; c < ncmax+1; c++) { cx = cx + 2*r2;

THREAD IN JAVA 31 g.draw (new Ellipse2D.Duoble (cx, cy, r2, r2)); I metodi della classe Frame_xThread che sottendono alla trasformazione delle configurazioni delle regioni rgnprod e rgncons sono i metodi produci() e consuma(). Il metodo produci() opera sulla regione rgnprod: la chiamata riduce progressivamente di un unità il numero di dischi presenti nella regione, richiede la tracciatura della nuova configurazione della stessa e si sospende per 1000 millisecondi. In modo analogo il metodo consuma() opera sulla regione rgncons, incrementando progressivamente di un unità il numero di dischi della regione, richiedendo la tracciatura della nuova configurazione raggiunta e sospendendosi per uno stesso periodo temporale. public class Frame_xThread extends JFrame implements Runnable { public synchronized void produci() { for (int c = 0; c < ncmax; c++) { rgnprod.ncerchi ; rgnprod.repaint(); try { Thread.sleep (1000); catch (InterruptedException e) { throw new RuntimeException(); public /* synchronized */ void consuma() { for (int c = 0; c < ncmax; c++) { rgncons.ncerchi++; rgncons.repaint(); try { Thread.sleep (1000); catch (InterruptedException e) { throw new RuntimeException(); public void run() { consuma(); La sospensione del thread è ottenuta in entrambi i metodi produci() e consuma() con l uso del metodo statico sleep() della classe Thread, che è utilizzabile dal momento che la classe Frame_xThread implementa l interfaccia Runnable. Si consideri che la sospensione del thread è inserita sia per permettere all osservatore di seguire l evoluzione del processo di trasformazione nella distribuzione dei dischi nelle due regioni, sia per rendere palese il diverso funzionamento dell applicazione quando i metodi produci() e consuma() sono entrambi dichiarati sincronizzati rispetto a quando non lo sono.

32 CAPITOLO 18 La classe Frame_xThread è completata dal metodo run() contenente il codice dei thread da essa istanziabili. Come si nota, il thread si limita ad operare una chiamata al metodo consuma() per modificare la regione rgncons della GUI. Codice del thread public class Frame_xThread extends JFrame implements Runnable { public void run() { consuma(); La trasformazione complessiva delle configurazioni di dischi nelle due regioni di tracciatura del frame proviene dalla coordinata esecuzione di entrambi i metodi produci() e consuma(). La classe Pgm_xThread ne implementa la realizzazione. import java.awt.*; public class Pgm_xThread { public static void main (String[] args) { Frame_xThread winthread = new Frame_xThread(); Thread th1 = new Thread (winthread); th1.start(); winthread.produci(); L applicazione genera inizialmente un thread, denominato th1, creando un istanza della classe Frame_xThread e passando l oggetto winthread così istanziato al costruttore Thread() della omonima classe. Generazione thread public static void main (String[] args) { Frame_xThread winthread = new Frame_xThread(); Thread th1 = new Thread (winthread); Il thread th1 viene quindi attivato con il comando th1.start() per l esecuzione del metodo consuma(). Attivazione thread: consuma() public static void main (String[] args) { th1.start(); Infine viene effettuata l attivazione del metodo produci() dello stesso thread inoltrandone la richiesta all istanza winthread della classe Frame_xThread.

THREAD IN JAVA 33 Attivazione metodo: produci() public static void main (String[] args) { winthread.produci(); 18.5 Sincronizzazione dei thread Un aspetto importante dei sistemi multi-thread riguarda il trasferimento di dati tra processi diversi. Sull argomento la letteratura riporta numerose tecniche e soluzioni. Indipendentemente dalla metodologia seguita, va sottolineato che lo scambio dati fra thread richiede che gli stessi vengano sottoposti ad una fase di sincronizzazione. La tecnica presentata nel seguito si basa sulla presenza di monitor. Un monitor costituisce una risorsa software che viene condivisa fra i thread operanti lo scambio dati, in modo da forzare gli stessi a sincronizzarsi. La Figura 18.8 riporta due situazioni tipiche. Nella forma più semplice, presentata in Figura 18.8 (a), il monitor funge da sincronizzatore dei thread thd1 e thd2 durante lo scambio dati: la sua funzione è di acquisire il dato trasmesso dal thread mittente e di sospendere quest ultimo sino a che il thread destinatario lo non avrà ricevuto. La situazione è del tutto generalizzabile. A titolo dimostrativo, in Figura 18.8 (b) esistono più thread mittenti, denominati thdp1,, thdpn, e un thread ricevente thdc. In questo caso il monitor acquisisce un dato dal primo thread mittente pronto a trasmettere, ne sospende l attività al pari dei rimanenti thread mittente, così da impedire la sovrapposizione di dati diversi in ingresso, inoltra il dato al thread ricevente thdc dopo averlo risvegliato dal suo eventuale stato di sospensione. Ciò fatto, il monitor si rende disponibile per un successivo scambio dati sospendendo il thread ricevente e risvegliando tutti i thread mittente che erano stati precedentemente sospesi. Le operazioni di cui si è trattato richiedono che il linguaggio di implementazione disponga di primitive in grado di agire sull esecuzione dei thread, forzandone la sospensione e la successiva riattivazione. In Java ciascun oggetto può costituire una risorsa condivisa da più Figura 18.8 Utilizzazione dei monitor per la sincronizzazione di thread.

34 CAPITOLO 18 thread, per cui i metodi destinati alla loro sincronizzazione devono essere insiti negli oggetti stessi. Essi entrano quindi di diritto nella classe Object del package java.lang da cui discendono le classi di applicazione. Object void wait() native void wait (long timeout) native void notify() native void notifyall() Il primo metodo fornito dalla classe presiede alle operazioni di sospensione dei thread. Un thread sospeso perde il possesso della risorsa condivisa su cui opera e viene inserito nella coda dei processi in attesa. I metodi wait() sospendono il thread correntemente in esecuzione, facendogli perdere il controllo della risorsa condivisa in quel momento posseduta. I thread sospesi e in attesa del rilascio di una risorsa condivisa possono essere risvegliati da altro thread operante sulla stessa risorsa con il metodo notify() oppure con il metodo notifyall(). I thread così risvegliati competono per riottenere il controllo della risorsa in base alla loro priorità e/o al momento della loro sospensione. Nella seconda forma, il metodo wait() può limitare la sospensione del thread corrente al tempo indicato dal parametro timeout. In entrambe le forme il metodo può sollevare un eccezione di tipo InterruptedException. Due ulteriori metodi sono destinati alle operazioni di risveglio dei thread. Un thread risvegliato è trasferito nella coda dei processi pronti e può competere per ottenere nuovamente il possesso della risorsa condivisa di interesse. Il metodo notify() risveglia un singolo thread che è stato sospeso in attesa del rilascio di una risorsa condivisa. Il thread può ottenere il possesso della risorsa eseguendo un metodo sincronizzato appartenente alla risorsa stessa. Il metodo notifyall() risveglia tutti i thread che sono stati sospesi in attesa del rilascio di una risorsa condivisa. I metodi risvegliati competono per riottenere il controllo della risorsa in base alla loro priorità e/o al momento della loro sospensione. 18.5.1 Un esempio di sincronizzazione di thread Il problema proposto nel Paragrafo 18.4.1 si interessava della sincronizzazione di metodi in un modello di processo denominato produttore-consumatore. Quell esempio può essere generalizzato e completato in riferimento allo scambio dati fra thread diversi. La classe Frame_xThreads (Scheda 18.5a) fornisce un esempio di sincronizzazione di thread per l implementazione di un modello di scambio dati basato su monitor. Due thread produttori di dischi, di colori rispettivi blu e rosso, inoltrano i dischi da essi inizialmente posseduti ad un thread consumatore con cadenze temporali distinte. I thread sono costituiti da istanze delle classi Produttore e Consumatore. Per evitare conflittualità fra i diversi processi, lo scambio dati avviene attraverso una risorsa condivisa costituita da un istanza della classe Monitor. La sua funzione è di sincronizzare le operazioni di trasferimento dei dischi dai due thread produttori verso il thread consumatore.

THREAD IN JAVA 35 Figura 18.9 Architettura d insieme dell applicazione. La Figura 18.9 riporta l architettura software complessiva. Ai sottostanti thread produttori e consumatore, che costituiscono il nucleo dell applicazione, è associata un interfaccia grafica ripartita in tre regioni di tracciatura distinte, una per ciascun thread, con il compito di visualizzarne istante per istante lo stato. Si osservi che le diverse componenti software coinvolte nell applicazione sono tutte nettamente separate tra di loro, il che comporta una più semplice progettazione e linearità nella definizione delle classi. La classe principale dell applicazione è ora responsabile solamente della costruzione della GUI, suddivisa nelle tre regioni rgncons, rgnprod1 e rgnprod2, e dei metodi responsabili della visualizzazione dello stato dei diversi thread, mentre le operazioni di basso livello necessarie per la tracciatura dei dischi competono ad una ulteriore classe PanThread derivata da Canvas. Il cuore dell applicazione è costituito dalle classi Produttore e Consumatore, dalle quali sono generati gli omonimi thread, e la classe Monitor da cui istanziare il monitor necessario per la sincronizzazione delle attività dei processi. Il monitor infatti costituisce una risorsa condivisa dai thread produttori e consumatore, che si comporta come un supporto intermedio di trasferimento per i dischi. È basato su due metodi synchronized, denominati ricezione() e rilascio(), il cui funzionamento è l uno il duale dell altro: il metodo ricezione() è invocato dai thread della classe Produttore per inoltrare al monitor i propri dischi, mentre il metodo rilascio() è destinato ai thread della classe Consumatore per ricevere dal monitor i dischi in esso presenti.

36 CAPITOLO 18 Scheda 18.5a - class Argomenti class Frame_xThreads Sincronizzazione di thread. Modello implementato: monitor. Classi: Produttore, Consumatore, Monitor. import java.awt.*; import java.awt.event.*; import java.util.*; import java.awt.geom.*; import javax.swing.*; public class Frame_xThreads extends JFrame { private final int RGN_CONS = 0, RGN_PROD1 = 1, RGN_PROD2 = 2; private PanThread rgncons, rgnprod1, rgnprod2; public Frame_xThreads() { setdefaultcloseoperation (JFrame.EXIT_ON_CLOSE); Container cnt = getcontentpane(); cnt.setbackground (Color.white); cnt.setlayout (new BorderLayout()); settitle ( Produttori - Consumatore ); setbounds (30, 30, 700, 500); rgnprod1 = new PanThread(); rgnprod1.setsize (340, 300); rgnprod1.setbackground (Color.yellow); cnt.add (rgnprod1, West ); rgnprod2 = new PanThread(); rgnprod2.setsize (340, 500); rgnprod2.setbackground (Color.yellow); cnt.add (rgnprod2, East ); rgncons = new PanThread(); rgncons.setsize (700, 200); rgncons.setbackground (Color.lightGray); cnt.add (rgncons, South ); setvisible (true true); public void modificaregione (int rgn, int nmaxcerchi, Vector vt) { switch (rgn) { case 0: rgncons.setelements (vt, nmaxcerchi); rgncons.repaint(); break; case 1: rgnprod1.setelements (vt, nmaxcerchi); rgnprod1.repaint(); break; case 2: rgnprod2.setelements (vt, nmaxcerchi); rgnprod2.repaint(); break;