Programmazione concorrenteinJava damateriale di CarloGhezzi eAlfredoMotta Parallelismo =“multitasking” • Possiamoscrivereunprogrammaincuidiverse attività(task)evolvonoinparallelodaunpuntodi vistafisicoologico • Massimoparallelismofisico – Ogniattivitàparallelahaadisposizioneunprocessore fisico • Altrimentivengonoeseguitedaprocessori condivi – Secondomodalitàdecisedaunoscheduler,in generalenoncontrollabiledalprogrammatore Multitaskingsusingoloprocessore • Approccio – Processoreesegueuntask – Passavelocementeaunaltro – Sottoilgovernodelloscheduler • Ilprocessoresembralavoraresuidiversitask concorrentemente • Passaggiodauntaskall’altroneimomentidi inattivitàoperesaurimentodellafinestra temporale(“timesharing”) Caso 1:Tasksingolo Caso 2:Duetask 4 Multitaskingalivellodiprocessi • Processo – Programmaeseguibilecaricatoinmemoria – Haunsuospaziodiindirizzi(variabiliestrutture datiinmemoria) – Ogniprocessoesegueundiversoprogramma – IprocessicomunicanoviaSO,file,rete – Puòcontenerepiùthread Multitaskingalivellodithread • Threadèun’attivitàlogicasequenziale • Unthreadcondividelospaziodiindirizzicon glialtrithreaddelprocessoecomunicavia variabilicondivise • Haunsuocontestodiesecuzione(program counter,variabililocali) • Siparlaspessodiprocessolight-weight ThreadinJava- metodo1 ClasseThread start avviailthread(eseguendoilmetodorun) join chiamatosuunthreadspecificoehaloscopodi mettereinattesailthreadattualmentein esecuzionefinoaquandoilthreadsucuièstato invocatoilmetodojoin()nontermini isAlive controllaseilthreadèvivo(inesecuzione,inattesa obloccato) sleep(intms) sospendel’esecuzionedelthread yield mettetemporaneamenteinpausailthread correnteeconsenteadaltrithreadinstato Runnable(qualoravenesiano)diavereunachance peressereeseguiti public class A extends Thread { public void run() { for (int i=0; i<=5; i++) System.out.println("From Thread A: i= "+i); System.out.println("Exit from A"); } } public class B extends Thread { public void run() { for (int j=0; j<=5; j++) System.out.println("From Thread B: j= ""+j); System.out.println("Exit from B"); } } public class C extends Thread { public void run() { for (int k=0; k<=5; k++) System.out.println("From Thread C: k= "+k); System.out.println("Exit from C"); } } public class ThreadTest { public static void main(String[] args) { new A().start(); new B().start(); new C().start(); } } Outputpossibili Caso1 From From From From From From Exit From From From From From From Exit From From From From From From Exit Thread Thread Thread Thread Thread Thread from A Thread Thread Thread Thread Thread Thread from B Thread Thread Thread Thread Thread Thread from C Caso2 A: A: A: A: A: A: i= i= i= i= i= i= 0 1 2 3 4 5 B: B: B: B: B: B: j= j= j= j= j= j= 0 1 2 3 4 5 C: C: C: C: C: C: k= k= k= k= k= k= 0 1 2 3 4 5 From From From From From From Exit From From From From From From From From From From From From Exit Exit Thread Thread Thread Thread Thread Thread from A Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread from B from C A: A: A: A: A: A: i= i= i= i= i= i= 0 1 2 3 4 5 C: C: C: C: C: C: B: B: B: B: B: B: k= k= k= k= k= k= j= j= j= j= j= j= 0 1 2 3 4 5 0 1 2 3 4 5 ThreadinJava– metodo2 • LaclasseThreadinrealtàimplementa un’interfacciachiamataRunnable • L’interfacciaRunnabledefinisceunsolo metodorunchecontieneilcodicedelthread • Suciòsibasaunmodoalternativo ThreadinJava—metodo2 • metodopiùgeneralesiusasesideve ereditaredaqualcheclasse Daticondivisi • Può essere necessario imporre che certe sequenze di operazioni che accedono adati condivisi vengano eseguite dai taskinmutua esclusione class ContoCorrente { private float saldo; public ContoCorrente(float saldoIniz){ saldo=saldoInizi; public void deposito(float soldi){ saldo += soldi;} public void prelievo (float soldi){ saldo -=soldi;} ... Interferenza • Fenomenocausatodall’interleavingdioperazioni didueopiùthread – Letturadiy – Esecuzioneespressione – Scritturainx • L’esecuzioneconcorrentedix+=yex-=y puòavereunodeiseguentieffetti – incrementarexdiy – lasciareximmutata – decrementarexdiy Sequenze“atomiche” • Generalizzazionedelproblema – avoltesivuolechecertesequenzediistruzioni venganoeseguiteinisolamento,senza interleavingconistruzionidialtresequenze parallelechealtrithreadpotrebberoeseguire – siparladi“sequenzeatomiche” Altroproblemadiconcorrenza • Avolte,oltre avoler l’atomicità dicerte sequenze,si vogliono imporre certi ordinamenti nell’esecuzione dioperazioni • Peresempio,che l’operazione Aeseguita da unthreadvenga eseguita prima dell’operazione Bdiunaltro thread • Disolito ciò deriva dalfatto divoler garantire certe proprietà diconsistenza dei dati Comerendereimetodi"atomici" • Laparolachiave"synchronized” applicataa metodioblocchidicodice 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; } public class TaskA extends Thread { private SynchronizedCounter counter; } } public TaskA(SynchronizedCounter c) { counter = c; public void run(){ counter.increment(); System.out.println(counter.value()); } public class TaskB extends Thread { private SynchronizedCounter counter; public TaskB(SynchronizedCounter c) { counter = c; } } public void run(){ counter.decrement(); System.out.println(counter.value()); } public class ThreadTest { public static void main(String[] args) { SynchronizedCounter c = new SynchronizedCounter(); } } TaskA ta = new TaskA(c); TaskB tb = new TaskB(c); ta.start(); tb.start(); Metodisynchronized • Javaassocia unintrinsic lock aciascun oggetto – Ilockoperano alivello dithread • Quando il metodo synchronizedviene invocato – senessun metodo synchronizedè inesecuzione, l'oggetto viene bloccato (locked)equindi il metodo viene eseguito – sel'oggetto è bloccato,il taskchiamante viene sospeso fino aquando il taskbloccante libera il lock • Almassimounsingolothreadpuò trovarsi adeseguire istruzioni all’interno diuno stesso metodo synchronized Commentisullock • Diverseinvocazioni dimetodi synchronizedsullo stesso oggetto nonsono soggette ainterleaving • Icostruttori nonpossono essere synchronized – Soloil threadche crea l’oggetto deve avere accessoad esso mentre viene creato • Eventuali dati finalpossono essere letti con metodi nonsynchronized – Idati sono comunque insolalettura enonpossono essere modificati Ulterioricommentisullock • L’intrinsiclockvieneacquisitoautomaticamente all’invocazionedelmetodosynchronizede rilasciatoalritorno(sianormalecheeccezionale chedauncaughtexception) • Seilmetodosynchronizedfossestatic – Ilthreadacquisiscel’intrinsiclockperilClassobject associatoallaclasse – Pertantol’accessoaicampistaticècontrollatodaun lockspeciale,diversodaquelliassociatialleistanze dellaclasse Synchronizedstatements • Devonospecificarel’oggettoacuiapplicareillock public void addName(String name) { synchronized(this) { lastName = name; nameCount++; } nameList.add(name); } • Sirilasciaillockall’oggettoprimadiinvocareun metodochepotrebbeasuavoltarichiederedi attendereilrilasciodiunlock Controllo“fine” dellaconcorrenza • Esempio:classe conduecampi che nonvengono modificati mai insieme public class TestBlock { 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++;} } Alcuneregolepratiche • Usarelock perlamodificadegliattributi dell’oggetto – Peresserecertidiavereunostatoconsistente • Usarelock perl’accessoacampidell’oggetto probabilmentemodificati – Perevitaredileggerevalori“vecchi” • Nonusarelock quandosiinvocaunmetodosu altrioggetti – Perevitaredeadlock • Nonc’èbisognodilock peraccederealleparti “stateless” diunmetodo Liveness • Èunaproprietàmoltoimportanteinpratica • Significacheun’applicazioneconcorrente vieneeseguitaentroaccettabililimitiditempo • Lesituazionidaevitareattraversoun’attenta progettazionesono – deadlock,starvationelivelock Deadlock • Dueopiùthreadsonobloccatipersempre,in attesal’unodell’altro – Esempio:AnnaeGiacomosonoamiciecredono nelgalateo,chedicecheseunapersonasiinchina aunamico,deverestareinchinatafinoache l’amicorestituiscel’inchino • Problema:inchinoreciprocoallostessotempo public 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()); } public class ThreadTest { public static void main(String[] args) { 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(); Classianonime:approcio comodopercrearenuovi threadconunsemplice comportamento Starvation • Situazioneincuiunthreadhadifficoltàad accedereaunarisorsacondivisaequindiha difficoltàaprocedere • Esempio: – Task“greedy” chemoltofrequentemente invocanometodilunghiritardanocostantementeil thread – Unoschedulercheusaprioritàcedesempre precedenzaaitaskgreedy Livelock • Errore diprogetto che generauna sequenza ciclica dioperazioni inutili ai fini dell’effettivo avanzamento della computazione • Esempio: – Lasequenza infinita di“vada primalei” • Diverso daldeadlock:lacomputazione nonè bloccata,qualcosa viene fatto mamai niente diutile Guardedblocks • Comeevitareilprelievoseilcontovainrosso? public class ContoCorrente { private float saldo; } public synchronized void prelievo (float soldi){ while (saldo-soldi<0) wait(); saldo -= soldi; } Rilasciaillocke sospendeilthread! Comerisvegliareuntaskinwait? public class ContoCorrente { private float saldo; public ContoCorrente (float saldoI) { saldo = saldoI; } synchronized public void deposito (float soldi) { saldo += soldi; Risvegliauntask(sceltoa notify(); caso)insospesodawaitsu } questooggetto,seesiste } public synchronized void prelievo (float soldi) { while (saldo-soldi < 0) wait(); saldo -= soldi; Potrebbenonessere sufficiente...Ilthread } potrebberipartireanche senzaesserenotificatoda nessun’altrothread... Esempio:unacodaFIFOcondivisa • Operazione diinserimento dielemento – Sospende tasksecodapiena • while(codaPiena())wait(); – Altermine • notify(); • Operazione diestrazione dielemento – Sospende tasksecodavuota • while(codaVuota())wait(); – Altermine • notify(); Invece,notifyAllrisveglia tuttiitaskeventualmente unwaitsuun determinatooggetto,ma unosologuadagnaillock! public class FIFO { private boolean empty; private String message; public synchronized String take() { // Wait until message is available. while (empty) { try { wait(); } catch (InterruptedException e) {} } // Toggle status. empty = true; // Notify producer that status has changed. notifyAll(); return message; } } public synchronized void put(String message) { // Wait until message has been retrieved. while (!empty) { try { wait(); } catch (InterruptedException e) {} } // Toggle status. empty = false; // Store message. this.message = message; // Notify consumer that status has changed. notifyAll(); } Ciclodivitadiunthread born start notify notifyAll ready scheduler wait running I/O lock waiting dead fineI/O finelock blocked Problemiconoggettimutabili public class SynchronizedRGB { // Values must be between 0 and 255 private int red; private int green; private int blue; private String name; private void check(int red, int green,int blue) {//…} public SynchronizedRGB(int red, int green, int blue, String name) {//…} public void set(int red, int green, int blue, String name) { check(red, green, blue); synchronized (this) { this.red = red; this.green = green; Cambialostato this.blue = blue; dell’oggetto this.name = name; indipendentemente } daglialtrimetodi } “getter”.. } public synchronized int getRGB() {//…} public synchronized String getName () {//…} public synchronized void invert () {//…} Problema SynchronizedRGB color = new SynchronizedRGB(0,0,0, “Pitch Black”); ... int myColorInt = color.getRGB(); //Statement 1 String myColorName = color.getName(); //Statement 2 • Seunaltrothreadinvocaset dopoStatement1 maprimadiStatement2,ilvaloredimyColorInt noncorrispondealvaloredimyColorName • Ilproblemasorgeperchél’oggettoèmutabile Comecreareoggettiimmutabili • Nonfornire metodi "setter" • Definire tutti gli attributi diistanza finaleprivate • Nonconsentire alle sottoclassi difareoverridedei metodi – Dichiarando laclasse finaloppure dichiarando il costruttore privatee costruendo gli oggetti mediante factorymethod • Segli attributi diistanza hanno riferimenti aoggetti mutabili,non consentire laloro modifica – Nonfornire metodi che modificano oggetti mutabili – Nonfaresharingdirefaoggetti mutabili – Nonsalvare riferimenti aoggetti esterni mutabili passati al costruttore,senecessario farecopie esalvare riferimenti alle copie – Inoltre creare copie degli oggetti interni mutabili senecessario per evitare direstituire gli originali attraverso i metodi EsempioImmutableRGB • Cisonoduemetodisetter – Ilprimo(set)trasformaarbitrariamentel’oggettoenon avràalcuncorrispettivonellaversioneimmutabile – Ilsecondo,invert,vieneadattatocreandounnuovo oggettoinvecedimodificarel’oggettocorrente • Tuttigliattributisonogiàprivate;vengono ulteriormentequalificatifinal • Laclassevienequalificatafinal • Unsoloattributofariferimentoaunoggetto,e l’oggettoèimmutabile – Nonèquindinecessariofarnullapersalvaguardarelo statodieventualioggettimutabilicontenuti SoluzioneImmutableRGB final public class ImmutableRGB { // Values must be between 0 and 255. final private int red; final private int green; final private int blue; final private String name; private void check(int red, int green, int blue) { if (red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255) { throw new IllegalArgumentException(); } } } 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; } Concettiavanzati • Finora abbiamo visto i concetti dibase,che però risultano inadeguati aun’effettiva programmazione concorrente • Vediamo ora concetti introdotti in java.util.concurrent.* – Oggetti “lock” – Esecutori – Collezioni concorrenti – Variabili atomiche Oggetti“lock”- Lockobjects • Ilcodice synchronizeddefinisce uncaso elementare di lock(implicito),mameccanismi più sofisticati sono forniti dalpackagejava.util.concurrent.locks • Unlock,definito dall’interfaccia Lock,può essere acquisito daunsolothread,comenel caso degli “implicitlock” associati acodice synchronized ReentrantLock • AreentrantmutualexclusionLockwiththe samebasicbehaviorastheimplicitmonitor lockaccessedusingsynchronizedmethods andstatements,butwithextended capabilities – lock()acquiresthelock – tryLock()acquiresthelockonlyifitisnotheldby anotherthreadatthetimeofinvocation – unlock()Attemptstoreleasethislock Classe BowLoop import java.util.Random; public 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); } } Sospendeil thread corrente.... import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public 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; } Tentadiacquisire entrambiilock, tornatruese possibile, altrimentifalse Ricordatevidi rilasciareilock chepotresteaver parzialmente acquisito 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); Searriviamoqui } finally { lock.unlock(); abbiamoacquisito bower.lock.unlock(); entrambiilocke } dobbiamo } else { rilasciarli 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()); } public class ThreadTest { 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(); Tenterannodiinchinarsiinmomenti diversi,alcuniriusciranno,altrinoquando tentanodifarloallostessotempo Esecutori • Glistrumentifinoradisponibiliimpongonounastretta relazionetrailcompitochedeveessereeseguitodaun thread(definitodall’oggettoRunnable)eilthread stesso,definitodall’oggettoThread • Idueconcettipossonoesseretenutidistintiin applicazionicomplesse,mediante – interfacceExecutor – threadpools – fork/join • Gliesecutori sonopredefinitieconsentonouna gestioneefficientecheriduceilpesanteoverhead dovutoallagestionedeithread InterfacceExecutors • Ilpackagejava.util.concurrentdefinisce3interfacce – Executor – ExecutorService(estendeExecutor) – ScheduledExecutorService(estendeExecutorService) • UtilizzodiExecutor – SerèunRunnableedeèunExecutor,invecedi (newThread(r)).start(); facciamo e.execute(r); – …evitandol’overheaddovutoallacreazionedeglioggetti thread ImplementazionediExecutor • Leimplementazioni dell’interfaccia Executor usano threadpools,ovvero thread(innumero finito)che esistono aldifuori diRunnable • Unesempio comune è unexecutorche usa un “fixedthreadpool”,che viene creato chiamando il “factorymethod”newFixedThreadPool()della classe java.util.concurrent.Executors • Gli oggetti Runnablesono inviati alpool attraverso una coda – Sei threadesistenti sono tutti occupati,tocca aspettare… Collezioniconcorrenti • Ilpackagejava.util.concurrentinclude estensioniacollezionidiJava,quali – BlockingQueue • StrutturadatiFIFOchebloccaodàtimeoutsesicerca diinserireincodapienaoestrarredacodavuota – ConcurrentMap • Consenteinmanieraatomicadieliminare/modificare unacoppiachiave-valoresolosechiavepresentee aggiungerechiave-valoresoloseassente Variabiliatomiche • Ilpackagejava.util.atomicdefinisceclassiche supportanooperazioniatomichesusingole variabili • Esseposseggonotuttemetodigetesetchesi comportanocomeletturaescritturadelle corrispondentivariabilinonatomiche • Èdisponibileancheun’operazione compareAndSet Oggettiatomici • Consentonodievitareiproblemidilivenesschepossono esserecausatidall’usodeimetodisynchronized import java.util.concurrent.atomic.AtomicInteger; public class AtomicCounter { private AtomicInteger c = new AtomicInteger(0); public void increment() { c.incrementAndGet(); } public void decrement() { c.decrementAndGet(); } } public int value() { return c.get(); }