Dept. of Computer Science Lab 2: Sincronizzazione diretta/indiretta in Java Matteo Camilli [email protected] Laboratorio di Sistemi Operativi a.a. 2015/16 Università degli Studi di Bergamo 1 Esercizi Lab1 • Somma parallela 1 • https://bitbucket.org/sistemioperativiunibg/lab1-parallelsum1 • Somma parallela 2 • https://bitbucket.org/sistemioperativiunibg/lab1-parallelsum2 2 Accesso competitivo • Flussi di esecuzione paralleli accedono e modificano oggetti condivisi • Esempio • https://bitbucket.org/sistemioperativiunibg/lab2-sharedobject-nondeterminism • L’accesso competitivo necessita un meccanismo di sincronizzazione: • Sincronizzazione indiretta o di mutua esclusione • La mutua esclusione in Java si realizza tramite gli object lock • Ogni oggetto in Java possiede un lock • Ogni thread che deve effettuare delle operazioni concorrenti deve acquisire il lock sull’oggetto e rilasciare il lock quando ha terminato le operazioni • Fintanto che un thread possiede il lock su un oggetto, nessun altro thread può acquisirlo • Tutti gli altri thread che tentano di acquisire il lock vengono bloccati 3 Synchronized • Per acquisire il lock su un’oggetto in Java occorre eseguire dei blocchi/metodi synchronized. • Il lock viene rilasciato quando il thread esce dal blocco/metodo synchronized. • Il primo thread presente nell’entry set può acquisire il lock 4 Synchronized • Un metodo synchronized può chiamare un altro metodo synchronized sullo stesso oggetto senza provocare il blocco del thread corrente • Un metodo synchronized ed un metodo non synchronized possono essere eseguiti concorrentemente sullo stesso oggetto 5 Java Thread lifecycle / runnable 6 Interazione cooperativa • Diversi thread possono doversi sincronizzare per potersi scambiare informazioni • Esempio: PRODUTTORE/CONSUMATORE Un thread produttore produce dei dati che vengono accumulati all’interno di un buffer. Un altro thread consumatore legge e rimuove i dati dal buffer. • La cooperazione tra più thread necessita un meccanismo di sincronizzazione: • Sincronizzazione diretta: in Java avviene tramite due metodi: • wait() e notify() invocabili solo da un blocco/metodo synchronized • Il metodo wait() mette il thread corrente in attesa sull’oggetto su cui aveva acquisito il lock fino a che un altro thread che ha acquisito il lock sul medesimo oggetto invoca il metodo notify() / notifyAll() 7 Notify • Il metodo notify() provoca le seguenti azioni: • viene selezionato arbitrariamente un thread T dal wait set • T viene spostato dal wait set all’entry set • lo stato di T cambia cambia da waiting a blocking • T compete per il lock con gli altri thread • Il metodo notifyAll() risveglia tutti i thread nel wait set e i quali vengono aggiunti all’entry set 8 Sincronizzazione diretta: Esempio • https://bitbucket.org/sistemioperativiunibg/lab2-message 9 Esercizi • Bank Account: Correggere l’esempio del Bank Account in modo che l’accesso alle risorse condivise risulti in mutua esclusione. • Producer/Consumer: Due thread Producer e Consumer condividono un buffer di dimensioni prefissate. Il Producer genera alcuni dati e li mette nel Buffer. Allo stesso tempo il Consumer consuma i dati prodotti dal Producer (rimuove i dati dal buffer) uno alla volta. Il Producer non può aggiungere dati al buffer se è pieno. Il Consumer non può rimuovere i dati dal buffer se è vuoto. • Suggerimenti: • Il buffer può essere implementato ad esempio tramite un ArrayDeque<T> • stampare su stdout tutte le operazioni (wait, notify, produce, consume) eseguite dai thread per capire cosa succede durante l’esecuzione del programma • Advanced Producer/Consumer: modificare il programma precedente in modo che possa essere eseguito lanciando N Producer e M Consumer. 10 Esercizi • Circular Buffer ADT: Definire una classe che rappresenta un Abstract Data Type (ADT) CircularBuffer<T>. • Un buffer circolare viene inizializzato dando in input la lunghezza desiderata • operazione di push (inserimento di un elemento) • Il primo inserimento parte nella posizione 0, poi 1, 2, e così via • quando il buffer è pieno, l’inserimento di un elemento va a sostituire l’elemento più vecchio contenuto nel buffer • operazione di pop (rimozione di un elemento) • l’operazione rimuove l’elemento più vecchio contenuto nel buffer • se il buffer è vuoto viene generata una opportuna eccezione • operazione isEmpty • ritorna true se il buffer è vuoto, false altrimenti • Usare gli opportuni meccanismi di sincronizzazione per fare in modo che le operazioni eseguite sulla struttura dati avvengano in mutua esclusione. • Testare il corretto funzionamento eseguendo diverse operazioni concorrenti sulla medesima istanza di CircularBuffer da parte di diversi thread concorrenti. 11