Lab 2: Sincronizzazione diretta/indiretta in Java

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