Sincronizzazione con Java Corse critiche e sincronizzazione • • Java implementa un meccanismo simile al monitor per garantire la sincronizzazione fra thread Ogni oggetto ha un lock associato ad esso – Nelle classi possono essere definiti metodi synchronized synchronized int myMethod() – Un solo thread alla volta può eseguire un metodo synchronized – Se un oggetto ha più di un metodo sincronizzato, comunque uno solo può essere attivo • NB: non vi è nessun controllo su metodi non sincronizzati Sistemi Operativi 16/17 Sincronizzazione con Java 2 Metodi synchronized • • • La chiamata di un metodo sincronizzato richiede il “possesso” del lock Se un thread chiamante non possiede il lock (un altro thread ne è già in possesso), il thread viene sospeso in attesa del lock Il lock è rilasciato quando un thread esce da un metodo sincronizzato lock owner Thread in attesa del lock Sistemi Operativi 16/17 Sincronizzazione con Java 3 Metodi synchronized synchronized void incrementaContatore() { int tmp = contatore + 1; if(tmp>maxContatore) maxContatore = tmp; aspetta(milliAspetta); contatore = tmp; } void synchronized decrementaContatore() { int tmp = contatore; aspetta(milliAspetta); contatore = tmp - 1; } Sistemi Operativi 16/17 Sincronizzazione con Java 4 Sincronizzazione • Un Thread può decidere di non proseguire l’esecuzione – Può allora volontariamente chiamare il metodo wait all’interno di un metodo sincronizzato • chiamare wait in un contesto diverso genera un errore Sistemi Operativi 16/17 Sincronizzazione con Java 5 Il metodo wait() Quando un thread esegue wait(), si verificano i seguenti eventi: 1. il thread rilascia il lock 2. il thread viene bloccato 3. il thread è posto in una coda di attesa 4. gli altri thread possono ora competere per il lock lock owner Thread in bloccati (wait) Thread in attesa del lock Sistemi Operativi 16/17 Sincronizzazione con Java 6 Il metodo notify() Nel momento in cui una condizione che ha provocato una attesa si è modificata si può riattivare un singolo thread in attesa tramite notify Quando un thread richiama notify(): 1) 2) 3) viene selezionato un thread arbitrario T fra i thread bloccati T torna fra i thread in attesa del lock T diventa Eseguibile Sistemi Operativi 16/17 Sincronizzazione con Java 7 insert() con wait/notify public synchronized void insert(Object item) { if(count==BUFFER_SIZE) { try { wait(); } catch(InterruptedException e) {} } ++count; buffer[in] = item; in = (in + 1) % BUFFER_SIZE; if(count==1) notify(); } Sistemi Operativi 16/17 Sincronizzazione con Java 8 Sincronizzazioni multiple • • • • notify() seleziona un thread arbitrario. Non sempre questo è il comportamento desiderato. Java non permette di specificare il thread che si vuole selezionare notifyAll() “risveglia” tutti i thread in attesa. In questo modo si permette ai thread stessi di decidere chi deve proseguire. notifyAll() è una strategia conservativa utile quando più thread sono in attesa Sistemi Operativi 16/17 Sincronizzazione con Java 9 insert() con wait/notifyAll public synchronized void insert(Object item { while(count==BUFFER_SIZE) { try { wait(); } catch(InterruptedException e) {} } ++count; buffer[in] = item; in = (in + 1) % BUFFER_SIZE; if(count==1) notifyAll(); } private int count=0; Sistemi Operativi 16/17 Sincronizzazione con Java 10 Confronto Monitor / Java • • • • • Monitor Un solo processo può entrare nel monitor ed eseguire una sua procedura Variabili condizione wait(variabile_condizione) signal(var. condizione) Sistemi Operativi 16/17 Sincronizzazione con Java • • • • • (Qualunque) oggetto Un solo thread alla volta può eseguire uno dei metodi synchronized Una sola implicita (this) this.wait() this.notify() / notifyAll() 11 remove() con wait/notify public synchronized Object remove() { while(count==0) { try { wait(); } catch(InterruptedException e) {} } --count; Object item = buffer[out]; out = (out + 1) % BUFFER_SIZE; if(count==BUFFER_SIZE-1) notify(); return item; } Sistemi Operativi 16/17 Sincronizzazione con Java 13 Metodo sleep static void sleep( millisecondi ) Il Thread si sospende (non richiede l’uso del processore) per un certo numero di millisecondi Nel frattempo possono andare in esecuzione thread a bassa priorità NB sleep non restituisce il possesso del lock Sleep è un metodo statico della classe Thread Wait è un metodo della classe Object Sistemi Operativi 16/17 Sincronizzazione con Java 15 Sincronizzazione di blocco • Si definisce scope di un lock è il tempo fra la sua acquisizione ed il suo rilascio • Tramite la parola chiave synchronized si possono indicare blocchi di codice piuttosto che un metodo intero • Ne risulta uno scope generalmente più piccolo rispetto alla sincronizzazione dell'intero metodo Sistemi Operativi 16/17 Sincronizzazione con Java 16 Sincronizzazione di blocco Object mutexLock = new Object(); public void someMethod() { nonCriticalSection_1(); synchronized(mutexLock) { criticalSection(); } nonCriticalSection_2(); } Sistemi Operativi 16/17 Sincronizzazione con Java 17 Sincronizzazione di blocco public class TestRunnableSync implements Runnable { int count; public void run() { int tmp; synchronized (this) { tmp = count++; } String name = Thread.currentThread().getName(); System.out.println(name + " " + tmp); } public static void main(String[] args) { int n=args.length>0?Integer.parseInt(args[0]):10; Runnable runnable = new TestRunnableSync(); for(int i=0; i<n; i++) { Thread th = new Thread(runnable); th.start(); } } } Sistemi Operativi 16/17 Sincronizzazione con Java 18 Versione non sincronizzata public class TestRunnableSync implements Runnable { int count; public void run() { int tmp; tmp = count++; String name = Thread.currentThread().getName(); System.out.println(name + " " + tmp); } public static void main(String[] args) { int n=args.length>0?Integer.parseInt(args[0]):10; Runnable runnable = new TestRunnableSync(); for(int i=0; i<n; i++) { Thread th = new Thread(runnable); th.start(); } } } Sistemi Operativi 16/17 Sincronizzazione con Java 19 TestRunnableSync TestRunnableSync Un solo oggetto Runnable count Thread Thread Thread Thread ... N oggetti Thread Sistemi Operativi 16/17 Sincronizzazione con Java 20 Sincronizzazione di blocco public class TestRunnableSync2 implements Runnable { int count; public void run() { int tmp; synchronized (this) { tmp = count++; } String name = Thread.currentThread().getName(); System.out.println(name + " " + tmp); } public static void main(String[] args) { int n=args.length>0?Integer.parseInt(args[0]):10; for(int i=0; i<n; i++) { Runnable runnable = new TestRunnableSync(); Thread th = new Thread(runnable); th.start(); } } } Sistemi Operativi 16/17 Sincronizzazione con Java 21 TestRunnableSync2 N oggetti Runnable TestRunnableSync TestRunnableSync TestRunnableSync count count count Thread Thread Thread TestRunnableSync ... count Thread ... N oggetti Thread Sistemi Operativi 16/17 Sincronizzazione con Java 22 Daemon Threads • Sono thread – Che effettuano operazioni ad uso di altri thread • Per esempio: Garbage collector – Sono eseguiti in background • Usano cioè il processore solo quando altrimenti il tempo macchina andrebbe perso – A differenza degli altri thread non impediscono la terminazione di una applicazione • Quando sono attivi solo thread daemon il programma termina – Occorre specificare che un thread è un daemon prima dell’invocazione di start - setDaemon( true ); – Il metodo isDaemon • Restituisce true se il thread è un daemon thread Sistemi Operativi 16/17 Sincronizzazione con Java 23 La classe Timer • La classe java.util.Timer permette la schedulazione di processi nel futuro (attenzione esiste anche la classe javax.swing.Timer dal comportamento parzialmente diverso) Costruttore: Timer() void schedule(TimerTask task, long delay, long period) – Permette di eseguire un task periodico dopo un ritardo specificato – Esistono altri costruttori e altri metodi (si veda scheduleAtFixedRate ) Sistemi Operativi 16/17 Sincronizzazione con Java 25 La classe TimerTask • TimerTask è una classe astratta occorre estenderla e definire il metodo run (ciò che il task deve fare) long periodo = 1000; // per esempio un secondo java.util.Timer timer = new java.util.Timer(); timer.schedule(new MyTimerTask(), 0, periodo); public class MyTimerClass extends java.util.TimerTask { public void run() { // ciò che devo fare } dovrebbe richiedere un tempo inferiore a periodo } Sistemi Operativi 16/17 Sincronizzazione con Java 26 Il barbiere dormiente • • • Sistemi Operativi 16/17 Soluzione java con Lock e Condition È possibile definire più condizioni – wait → await – notify → signal Cosa accade se i barbieri sono più di uno? IPC 42 Soluzione import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class SleepingBarberUsingReentrantLock extends Thread { public static final int CHAIRS = 5; public int numberOfFreeSeats = CHAIRS; // customers waiting for their hair cut. public Customers customers = new Customers(); // a ReentrantLock for barber with a condition // to wait if the barber is not available public Lock barber = new ReentrantLock(); public Condition barberAvailable = barber.newCondition(); // ReentrantLock for chairs so that we can increment the counter safely public Lock accessSeats = new ReentrantLock(); Sistemi Operativi 16/17 IPC 43 Soluzione void customer() { accessSeats.lock(); // tries to get access to the chairs if (numberOfFreeSeats > 0) { // if there are any free seats System.out.println(getName() + " just sat down."); numberOfFreeSeats--; // sitting down on a chair customers.releaseCustomer(); // notify the barber that there is a customer accessSeats.unlock(); // don't need to lock the chairs barber.lock(); // we have to wait if the barber is busy try { barberAvailable.await(); } catch (InterruptedException e) { } finally { barber.unlock(); } get_haircut(); // cutting... } else { // there are no free seats System.out.println("There are no free seats. " + getName() + " has left the accessSeats.unlock(); // release the lock on the seats } } Sistemi Operativi 16/17 IPC 44 Soluzione void barber() { while (true) { try { // tries to acquire a customer - if none is available he goes to sleep customers.acquireCustomer(); // at this time he has been awaken // want to modify the number of available seats accessSeats.lock(); numberOfFreeSeats++; // one chair gets free barber.lock(); try { barberAvailable.signal(); // the barber is ready to cut } finally { barber.unlock(); } accessSeats.unlock(); cutHair(); // cutting... } catch (InterruptedException ex) {} } } Sistemi Operativi 16/17 IPC 45 Problemi di IPC • • • • • Implementare una “barriera” Implementare lo scambio di messaggi Implementare un algoritmo di scheduling (decidere cioè quale processo può continuare l’elaborazione) Realizzare un semaforo stradale Studiare un algoritmo di allocazione di memoria (processi che chiedono e rilasciano memoria, un processo che la gestisce) Sistemi Operativi 16/17 Sincronizzazione con Java 56 SleepingBarberUsingReentrantLock.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; 09/04/2017 public class SleepingBarberUsingReentrantLock extends Thread { public static final int CHAIRS = 5; public int numberOfFreeSeats = CHAIRS; // a Customer pool which will be waiting for their hair cut. public CustomerPool customers = new CustomerPool(CHAIRS); // a ReentrantLock for barber with a condition to wait if the barber is not available public Lock barber = new ReentrantLock(); public Condition barberAvailable = barber.newCondition(); // ReentrantLock for chairs so that we can increment the counter safely public Lock accessSeats = new ReentrantLock(); class Customer extends Thread { public Customer(int i) { super("Customer-" + i); } public void run() { accessSeats.lock(); // tries to get access to the chairs if (numberOfFreeSeats > 0) { // if there are any free seats System.out.println(getName() + " just sat down."); numberOfFreeSeats--; // sitting down on a chair 1 SleepingBarberUsingReentrantLock.java 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 } } 09/04/2017 customers.releaseCustomer(); // notify the barber that there is a customer accessSeats.unlock(); // don't need to lock the chairs barber.lock(); try { // now it's this customers turn but we have to wait if the barber is busy barberAvailable.await(); } catch (InterruptedException e) { } finally { barber.unlock(); } get_haircut(); // cutting... } else { // there are no free seats System.out.println("There are no free seats. " + getName() + " has left the barbershop."); accessSeats.unlock(); // release the lock on the seats } /* this method will simulate getting a hair-cut */ public void get_haircut() { System.out.println(getName() + " is getting his hair cut"); try { sleep(5050); } catch (InterruptedException ex) {} } 2 SleepingBarberUsingReentrantLock.java 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 09/04/2017 class Barber extends Thread { public void run() { while (true) { try { // tries to acquire a customer - if none is available he goes to sleep customers.acquireCustomer(); // at this time he has been awaken -> want to modify the number of available seats accessSeats.lock(); numberOfFreeSeats++; // one chair gets free barber.lock(); try { barberAvailable.signal(); // the barber is ready to cut } finally { barber.unlock(); } accessSeats.unlock(); cutHair(); // cutting... } catch (InterruptedException ex) {} } } /* this method will simulate cutting hair */ public void cutHair() { System.out.println("The barber is cutting hair"); try { sleep(5000); } catch (InterruptedException ex) {} 3 SleepingBarberUsingReentrantLock.java 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 } 09/04/2017 } public static void main(String args[]) throws InterruptedException { new SleepingBarberUsingReentrantLock(args.length==0 ? 16 : Integer. parseInt(args[0])); } SleepingBarberUsingReentrantLock(int size) throws InterruptedException { Barber barber = new Barber(); barber.start(); Customer customers[] = new Customer[size]; for (int i=0; i<customers.length; i++) { customers[i] = new Customer(i); customers[i].start(); sleep(1000+(int) (2000*Math.random())); } for (int i=0; i<customers.length; i++) { customers[i].join(); } System.exit(0); } static class CustomerPool { private private private private final Lock lock = new ReentrantLock(); final Condition poolAvailable = lock.newCondition(); int num_customers; final int max_num_customers; 4 SleepingBarberUsingReentrantLock.java 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 public CustomerPool(int num_customer_pools) { this.max_num_customers = num_customer_pools; this.num_customers = 0; } public void acquireCustomer() throws InterruptedException { lock.lock(); try { while (num_customers <= 0) poolAvailable.await(); --num_customers; } finally { lock.unlock(); } } } } public void releaseCustomer() { lock.lock(); try { ++num_customers; poolAvailable.signal(); } finally { lock.unlock(); } } 5 09/04/2017