Programmazione concorrente in Java da materiale di Carlo Ghezzi e Alfredo Mo8a Parallelismo = “mul<tasking” • Possiamo scrivere un programma in cui diverse a?vità (task) evolvono in parallelo da un punto di vista fisico o logico • Massimo parallelismo fisico – Ogni a?vità parallela ha a disposizione un processore fisico • Altrimen< vengono eseguite da processori condivi – Secondo modalità decise da uno scheduler, in generale non controllabile dal programmatore Mul<tasking su singolo processore • Approccio – Processore esegue un task – Passa velocemente a un altro – So8o il governo dello scheduler • Il processore sembra lavorare sui diversi task concorrentemente • Passaggio da un task all’altro nei momen< di ina?vità o per esaurimento della finestra temporale (“<me sharing”) Caso 1: Task singolo Caso 2: Due task 4 Mul<tasking a livello di processi • Processo – Programma eseguibile caricato in memoria – Ha un suo spazio di indirizzi (variabili e stru8ure da< in memoria) – Ogni processo esegue un diverso programma – I processi comunicano via SO, file, rete – Può contenere più thread Mul<tasking a livello di thread • Thread è un’a?vità logica sequenziale • Un thread condivide lo spazio di indirizzi con gli altri thread del processo e comunica via variabili condivise • Ha un suo contesto di esecuzione (program counter, variabili locali) • Si parla spesso di processo light-‐weight Thread in Java -‐ metodo 1 Classe Thread start avvia il thread (eseguendo il metodo run) join un thread si me8e in a8esa della terminazione del thread su cui è invocato isAlive controlla se il thread è vivo (in esecuzione, in a8esa o bloccato) sleep(int ms) sospende l’esecuzione del thread yield sospende l’esecuzione del thread corrente Esempio Thread in Java – metodo 2 • La classe Thread in realtà implementa un’interfaccia chiamata Runnable • L’interfaccia Runnable definisce un solo metodo run che con<ene il codice del thread • Su ciò si basa un modo alterna<vo Thread in Java—metodo 2 • metodo più generale si usa se si deve ereditare da qualche classe Da< condivisi • Può essere necessario imporre che certe sequenze di operazioni che accedono a da< condivisi vengano eseguite dai task in mutua esclusione class ContoCorrente { private float saldo; public ContoCorrente (float saldoIniz) { saldo = saldoIniz; } public void deposito (float soldi) { saldo += soldi; } public void prelievo (float soldi) { saldo -= soldi; } … Interferenza • Fenomeno causato dall’interleaving di operazioni di due o più thread – Le8ura di y – Esecuzione espressione – Scri8ura in x • L’esecuzione concorrente di x+=y e x-‐=y può avere uno dei seguen< effe? – incrementare x di y – lasciare x immutata – decrementare x di y Sequenze “atomiche” • Generalizzazione del problema – a volte si vuole che certe sequenze di istruzioni vengano eseguite in isolamento, senza interleaving con istruzioni di altre sequenze parallele che altri thread potrebbero eseguire – si parla di “sequenze atomiche” Altro problema di concorrenza • A volte, oltre a voler l’atomicità di certe sequenze, si vogliono imporre cer< ordinamen< nell’esecuzione di operazioni • Per esempio, che l’operazione A eseguita da un thread venga eseguita prima dell’operazione B di un altro thread • Di solito ciò deriva dal fa8o di voler garan<re certe proprietà di consistenza della memoria (stato della computazione) Come rendere i metodi "atomici" • La parola chiave "synchronized” applicata a metodi o blocchi di codice class ContoCorrente { private float saldo; public ContoCorrente (float saldoIniz) { saldo = saldoIniz; } public synchronized void deposito (float soldi) { saldo += soldi; } public synchronized void prelievo (float soldi) { saldo -= soldi; } … Esempio public class SynchronizedCounter {! private int c = 0;! ! public synchronized void increment() {! c++;! }! ! public synchronized void decrement() {! c--;! }! ! public synchronized int value() {! return c;! }! } Metodi synchronized • Java associa un intrinsic lock a ciascun ogge8o – I lock operano a livello di thread • Quando il metodo synchronized viene invocato – se nessun metodo synchronized è in esecuzione, l'ogge8o viene bloccato (locked) e quindi il metodo viene eseguito – se l'ogge8o è bloccato, il task chiamante viene sospeso fino a quando il task bloccante libera il lock Commen< sul lock • Diverse invocazioni di metodi synchronized sullo stesso ogge8o non sono sogge8e a interleaving – quanto un thread X esegue un metodo synchronized di un ogge8o, i thread che eseguono invocazioni di metodi synchronized sullo stesso ogge8o vengono sospesi fino a che X termina l’esecuzione del metodo • I costru8ori non possono essere synchronized – Solo il thread che crea l’ogge8o deve avere accesso ad esso mentre viene creato – A8enzione a non far uscire un riferimento prematuro all’ogge8o • Eventuali da< final (che non possono essere modifica< dopo la creazione) possono essere le? con metodi non synchronized Ulteriori commen< sul lock • L’intrinsic lock viene acquisito automa<camente all’invocazione del metodo synchronized e rilasciato al ritorno (sia normale che eccezionale che da uncaught excep<on) • Se il metodo synchronized fosse sta<c – Il thread acquisisce l’intrinsic lock per il Class object associato alla classe – Pertanto l’accesso ai campi sta<c è controllato da un lock speciale, diverso da quelli associa< alle istanze della classe Synchronized statements • Devono specificare l’ogge8o a cui applicare il lock public void addName(String name) { synchronized(this) { lastName = name; nameCount++; } nameList.add(name); } • Si rilascia il lock all’ogge8o prima di invocare un metodo che potrebbe a sua volta richiedere di a8endere il rilascio di un lock Controllo “fine” della concorrenza • Esempio: classe con due campi che non vengono modifica< mai insieme public class XXX {! private long c1 = 0;! private long c2 = 0;! private Object lock1 = new Object();! private Object lock2 = new Object();! ! public void inc1() {! synchronized(lock1) {! c1++;! }! }! ! public void inc2() {! synchronized(lock2) {! c2++;! }! }! }! ! ! Alcune regole pra<che • Usare lock per la modifica degli a8ribu< dell’ogge8o – Per essere cer< di avere uno stato consistente • Usare lock per l’accesso a campi dell’ogge8o probabilmente modifica< – Per evitare di leggere valori “vecchi” • Non usare lock quando si invoca un metodo su altri ogge? – Per evitare deadlock • Non c`è bisogno di lock per accedere alle par< “stateless” di un metodo Liveness • È una proprietà molto importante in pra<ca • Significa che un’applicazione concorrente viene eseguita entro acce8abili limi< di tempo • Le situazioni da evitare a8raverso un’a8enta proge8azione sono – deadlock, starva<on e livelock Deadlock • Due o più thread sono blocca< per sempre, in a8esa l’uno dell’altro – Esempio: Anna e Giacomo sono amici e credono nel galateo, che dice che se una persona si inchina a un amico, deve restare inchinata fino a che l’amico res<tuisce l’inchino • Problema: inchino reciproco allo stesso tempo class Friend {! private final String name;! public Friend(String name) {! this.name = name;! }! public String getName() {! return this.name;! }! public synchronized void bow(Friend bower) {! System.out.format("%s: %s"! + " has bowed to me!%n", ! this.name, bower.getName());! bower.bowBack(this);! }! public synchronized void bowBack(Friend bower) {! System.out.format("%s: %s"! + " has bowed back to me!%n",! this.name, bower.getName());! }! } final Friend anna = new Friend(“Anna”);! final Friend giacomo = new Friend("Giacomo");! new Thread(new Runnable() {public void run() {anna.bow(giacomo);}}).start();! new Thread(new Runnable() {public void run() {giacomo.bow(anna);}}).start(); Starva<on • Situazione in cui un thread ha difficoltà ad accedere a una risorsa condivisa e quindi ha difficoltà a procedere • Esempio: – Task “greedy” che molto frequentemente invocano metodi lunghi ritardano costantemente il thread, o uno scheduler che usa priorità cede sempre precedenza ai task greedy Livelock • Errore di proge8o che genera una sequenza ciclica di operazioni inu<li ai fini dell’effe?vo avanzamento della computazione • Esempio: – La sequenza infinita di “vada prima lei” Guarded blocks • Come evitare il prelievo se il conto va in rosso? class ContoCorrente { private float saldo; … synchronized public void prelievo (float soldi) { while (saldo-soldi<0) wait(); saldo -= soldi; } … rilascia il lock sull'ogge8o e sospende il task Come risvegliare un task in wait? class ContoCorrente { private float saldo; public ContoCorrente (float saldoIniz) { saldo = saldoIniz; } synchronized public void deposito (float soldi) { saldo += soldi; risveglia un task sospeso in notify(); wait, se esiste nondeterminismo } synchronized public void prelievo (float soldi) { while (saldo-soldi<0) wait(); saldo -= soldi; Attenzione! } … if (saldo-soldi<0) wait(); Non è sufficiente Esempio: una coda FIFO condivisa • Operazione di inserimento di elemento – sospende task se coda piena • while (codaPiena()) wait(); – al termine • no<fy(); • Operazione di estrazione di elemento – sospende task se coda vuota • while (codaVuota()) wait(); – al termine • no<fy(); invece notifyAll risveglia tutti… MA uno solo guadagna il lock Schema produ8ore/consumatore public synchronized String take() {! // Wait until message is! // available.! while (empty) {! try {! wait();! } catch (InterruptedException e) {}! }! // Toggle status.! empty = true;! public synchronized void put(String message) {! // Notify producer that! // Wait until message has! // status has changed.! // been retrieved.! notifyAll();! while (!empty) {! return message;! try { ! } wait();! } catch (InterruptedException e) {}! }! // Toggle status.! empty = false;! // Store message.! this.message = message;! // Notify consumer that status! // has changed.! notifyAll();! } Ciclo di vita di un thread born start notify notifyAll ready scheduler wait running I/O lock waiting dead fine I/O fine lock blocked Problemi con ogge? mutabili public class SynchronizedRGB {! public void set(int red,! ! int green,int blue, String name) {! // Values must be between 0 and 255.! check(red, green, blue);! private int red;! synchronized (this) {! private int green;! this.red = red;! private int blue;! this.green = green;! private String name;! this.blue = blue;! ! this.name = name;! private void check(int red,! }! int green,int blue) {! }! if (red < 0 || red > 255! ! || green < 0 || green > 255! public synchronized int getRGB() {! || blue < 0 || blue > 255) {! return ((red << 16) | ! throw new IllegalArgumentException();! (green << 8) | blue);! }! }! }! ! ! public synchronized String getName(){! public SynchronizedRGB(int red,! return name;! int green, int blue, String name) {! }! check(red, green, blue);! ! this.red = red;! public synchronized void invert() {! this.green = green;! red = 255 - red;! this.blue = blue;! green = 255 - green;! this.name = name;! blue = 255 - blue;! } name = "Inverse of " + name;! }! } Spiegazione return ((red << 16) | (green << 8) | blue); //calcola un intero che rappresenta il colore con un intero //costruito facendo manipolazione delle rappresentazioni binarie //mediante bitwise inclusive or| (4|1 dà 5) // << X esegue shift a sinistra di X posizioni SynchronizedRGB color = new SynchronizedRGB(0, 0, 0, "Pitch Black"); ... int myColorInt = color.getRGB(); //Statement 1 String myColorName = color.getName(); //Statement 2 • Se un altro thread invoca color.set dopo Statement 1 ma prima di Statement 2, il valore di myColorInt non corrisponde al valore di myColorName • Il problema sorge perché l’ogge8o è mutabile Come creare ogge? immutabili • Non fornire metodi "se8er" • Definire tu? gli a8ribu< di istanza final e private • Non consen<re alle so8oclassi di fare override dei metodi – Dichiarando la classe final oppure dichiarando il costru8ore private e costruendo gli ogge? mediante factory method • Se gli a8ribu< di istanza hanno riferimen< a ogge? mutabili, non consen<re la loro modifica – Non fornire metodi che modificano ogge? mutabili – Non fare sharing di ref a ogge? mutabili – Non salvare riferimen< a ogge? esterni mutabili passa< al costru8ore, se necessario fare copie e salvare riferimen< alle copie – Inoltre creare copie degli ogge? interni mutabili se necessario per evitare di res<tuire gli originali a8raverso i metodi Esempio ImmutableRGB • Ci sono due metodi se8er – Il primo (set) trasforma arbitrariamente l’ogge8o e non avrà alcun corrispe?vo nella versione immutabile – Il secondo, invert, viene ada8ato creando un nuovo ogge8o invece di modificare l’ogge8o corrente • Tu? gli a8ribu< sono già private; vengono ulteriormente qualifica< final • La classe viene qualificata final • Un solo a8ributo fa riferimento a un ogge8o, e l’ogge8o è immutabile – Non è quindi necessario far nulla per salvaguardare lo stato di eventuali ogge? mutabili contenu< Soluzione ImmutableRGB final public class ImmutableRGB {! ! // Values must be between 0 and 255.! final private int red;! final private int green;! public int getRGB() {! final private int blue;! return ((red << 16) | ! final private String name;! (green << 8) | blue);! ! }! private void check(int red,! ! int green,! public String getName() {! int blue) {! return name;! if (red < 0 || red > 255! }! || green < 0 || green > 255! ! || blue < 0 || blue > 255) {! public ImmutableRGB invert() {! throw new IllegalArgumentException();! return new ImmutableRGB(255 - red,! }! 255 - green,! }! 255 - blue,! ! "Inverse of " + name);! public ImmutableRGB(int red,! }! int green,! } int blue,! String name) {! check(red, green, blue);! this.red = red;! this.green = green;! this.blue = blue;! this.name = name;! } Conce? avanza< • Finora abbiamo visto i conce? di base, che però risultano inadegua< a un’effe?va programmazione concorrente • Vediamo ora conce? introdo? con Java 5.0, in par<colare nei packages java.u<l.concurrent.* – Ogge? “lock” – Esecutori – Collezioni concorren< – Variabili atomiche Ogge? “lock”-‐ Lock objects • Il codice synchronized definisce un caso elementare di lock (implicito), ma meccanismi più sofis<ca< sono forni< dal package java.u<l.concurrent.locks • Un lock, definito dall’interfaccia Lock, può essere acquisito da un solo thread, come nel caso degli “implicit lock” associa< a codice synchronized • È però possibile ri<rarsi dalla richiesta di lock – il metodo trylock esce dal lock se questo non è disponibile (immediatamente o dopo un <meout da specificare) – il metodo lockInterrup<bly consente il ri<ro se un altro thread manda un interrupt prima che il lock sia acquisito Anna, Giacomo e l’inchino (creazione dei thread) public static void main(String[] args) {! final Friend giacomo = new Friend("Giacomo");! final Friend anna = new Friend("Anna");! new Thread(new BowLoop(giacomo, anna)).start();! new Thread(new BowLoop(anna, giacomo)).start();! } Anna, Giacomo e l’inchino (classe BowLoop) class BowLoop implements Runnable {! private Friend bower;! private Friend bowee;! ! public BowLoop(Friend bower, Friend bowee) {! this.bower = bower;! this.bowee = bowee;! }! ! public void run() {! Random random = new Random();! for (;;) {! try {! Thread.sleep(random.nextInt(10));! } catch (InterruptedException e) {}! bowee.bow(bower);! }! }! } Anna, Giacomo e l’inchino (classe Friend) class Friend {! res<tuisce vero se riesce ad acquisire …………………! il lock di ciascuno dei due a8ori che consentono public void bow(Friend bower) {! all’uno di inchinarsi e all’altro di res<tuire l’inchino if (impendingBow(bower)) {! try {! System.out.format("%s: %s has” + " bowed to me!%n", ! this.name, bower.getName());! bower.bowBack(this);! } finally {! lock.unlock();! bower.lock.unlock();! }! } else {! System.out.format("%s: %s started"! + " to bow to me, but saw that"! + " I was already bowing to"! + " him.%n”, this.name, bower.getName());! }! }! public void bowBack(Friend bower) {! System.out.format("%s: %s has" +! " bowed back to me!%n",! this.name, bower.getName());! }! } Anna, Giacomo e l’inchino (Classe Friend -‐ altri de8agli) class Friend {! …………….! public boolean impendingBow(Friend bower) {! Boolean myLock = false;! Boolean yourLock = false;! try {! myLock = lock.tryLock();! yourLock = bower.lock.tryLock();! } finally {! if (! (myLock && yourLock)) {! if (myLock) {! lock.unlock();! }! if (yourLock) {! bower.lock.unlock();! }! }! }! return myLock && yourLock;! }! Codice completo import java.util.concurrent.locks.Lock;! import java.util.concurrent.locks.ReentrantLock;! import java.util.Random;! public class Safelock {! static class Friend {! private final String name;! private final Lock lock = new ReentrantLock();! ! public Friend(String name) {! this.name = name;! }! public String getName() {! return this.name;! }! public boolean impendingBow(Friend bower) {! Boolean myLock = false;! Boolean yourLock = false;! try {! myLock = lock.tryLock();! yourLock = bower.lock.tryLock();! } finally {! if (! (myLock && yourLock)) {! if (myLock) {! lock.unlock();! }! if (yourLock) {! bower.lock.unlock();! }! }! }! return myLock && yourLock;! } Codice completo public void bow(Friend bower) {! if (impendingBow(bower)) {! try {! System.out.format("%s: %s has"! + " bowed to me!%n", ! this.name, bower.getName());! bower.bowBack(this);! } finally {! lock.unlock();! bower.lock.unlock();! }! } else {! System.out.format("%s: %s started"! + " to bow to me, but saw that"! + " I was already bowing to"! + " him.%n",! this.name, bower.getName());! }! }! ! public void bowBack(Friend bower) {! System.out.format("%s: %s has" +! " bowed back to me!%n",! this.name, bower.getName());! }! } static class BowLoop implements Runnable {! private Friend bower;! private Friend bowee;! public BowLoop(Friend bower, Friend bowee) {! this.bower = bower;! this.bowee = bowee;! }! ! public void run() {! Random random = new Random();! for (;;) {! try {! Thread.sleep(random.nextInt(10));! } catch (InterruptedException e) {}! bowee.bow(bower);! }! }! }! ! ! public static void main(String[] args) {! final Friend giacomo = new Friend("Giacomo");! final Friend anna = new Friend("Anna");! new Thread(new BowLoop(giacomo, anna)).start();! new Thread(new BowLoop(anna, giacomo)).start();! }! } Codice completo ! Esecutori • Gli strumen< finora disponibili impongono una stre8a relazione tra il compito che deve essere eseguito da un thread (definito dall’ogge8o Runnable) e il thread stesso, definito dall’ogge8o Thread • I due conce? possono essere tenu< dis<n< in applicazioni complesse, mediante – interfacce Executor – thread pools – fork/join • Gli esecutori sono predefini< e consentono una ges<one efficiente che riduce il pesante overhead dovuto alla ges<one dei thread Interfacce Executors • Il package java.u<l.concurrent definisce 3 interfacce – Executor – ExecutorService (estende Executor) – ScheduledExecutorService (estende ExecutorService) • U<lizzo di Executor – Se r è un Runnable ed e è un Executor, invece di (new Thread(r)).start(); facciamo e.execute(r); – evitando l’overhead dovuto alla creazione degli ogge? thread Implementazione di Executor • Le implementazioni dell’interfaccia Executor usano thread pools, ovvero thread che esistono al di fuori di Runnable • Un esempio comune è un executor che usa un “fixed thread pool”, che viene creato chiamando il factory method newFixedThreadPool della classe java.u<l.concurrent.Executors • I task sono invia< al pool a8raverso una coda Cenno al Fork/Join framework • È un’implementazione di ExecutorService u<le nel caso di più processori • Consente di distribuire task a elemen< di un thread pool secondo questo schema if (il mio task è abbastanza piccolo)! eseguo direttamente! else! spezzo il mio task in due task! invoco i due pezzi e attendo il risultato Collezioni concorren< • Il package java.u<l.concurrent include estensioni a collezioni di Java, quali – BlockingQueue • stru8ura da< FIFO che blocca o dà <meout se si cerca di inserire in coda piena o estrarre da coda vuota – ConcurrentMap • consente in maniera atomica di eliminare/modificare una coppia chiave-‐valore solo se chiave presente e aggiungere chiave-‐valore solo se assente Variabili atomiche • Il package java.u<l.atomic definisce classi che supportano operazioni atomiche su singole variabili • Esse posseggono tu8e metodi get e set che si comportano come le8ura e scri8ura delle corrisponden< variabili non atomiche • È disponibile anche un’operazione compareAndSet Esempio class Counter {! class SynchronizedCounter {! private int c = 0;! private int c = 0;! ! ! public void increment() {! public synchronized void increment() {! c++;! c++;! }! }! ! ! public void decrement() {! public synchronized void decrement() {! c--;! c--;! }! }! ! ! public int value() {! public synchronized int value() {! return c;! return c;! }! }! ! ! }! } • Come evitare problemi di interferenza? Ogge? atomici • Consentono di evitare i problemi di liveness che possono essere causa< dall’uso dei metodi synchronized import java.util.concurrent.atomic.AtomicInteger;! ! class AtomicCounter {! private AtomicInteger c = new AtomicInteger(0);! ! public void increment() {! c.incrementAndGet();! }! ! public void decrement() {! c.decrementAndGet();! }! ! public int value() {! return c.get();! }
© Copyright 2024 Paperzz