gruparea si sincronizarea firelor de executie.doc

Upload: bogdana-corcoz

Post on 04-Mar-2016

3 views

Category:

Documents


0 download

DESCRIPTION

JavaFire de executie

TRANSCRIPT

  • GrupareafirelordeexecutieGruparea firelor de executie pune la dispozitie un mecanism pentru manipularea acestora ca un tot si nu individual. De exemplu, putem sa pornim sau sa suspendam toate firele dintrun grup cu un singur apel de metoda.GrupareafirelordeexecutieserealizeazaprinintermediulclaseiThreadGroup.Fiecare fir de executie Java este mebmru al unui grup, indiferent daca specificam explicit acest lucru. Afilierea unui fir de executie la un anumit grup se realizeaza la crearea sa si devine permanenta, n sensul ca nu vom putea muta un fir de executie dintrun grup n altul, dupa ce acesta a fost creat. In cazul n care cream un fir de executie fara a specifica n constructor din ce grup face parte, el va fi plasat automat n acelasi grup cu firul de executie care la creat. La pornirea unui program Java se creeaza automat un obiect de tip ThreadGroupcu numele main, care va reprezenta grupul tuturor firelor de executie create direct din program si care nu au fost atasate explicit altui grup. Cu alte cuvinte, putem sa ignoram complet plasarea firelor de executie n grupuri si sa lasam sistemul sa se ocupe cu aceasta, adunndule pe toate n grupul main.Exista situatii cnd programul creeaza multe fire de executie iar gruparea lor poate usura substantial manevrarea lor. Crearea unui fir de executie si plasarea lui ntrun grup (altul dect cel implicit) se realizeaa prinurmatoriiconstructoriaiclaseiThread:

    publicThread(ThreadGroupgroup,Runnabletarget)publicThread(ThreadGroupgroup,Stringname)publicThread(ThreadGroupgroup,Runnabletarget,Stringname)

    Fiecare din acesti costructori creeaza un fir de executie, l initializeaza si l plaseaza ntrun grup specificat ca argument. In exemplul urmator vor fi create doua grupuri, primul cu doua fire de executie iar al doile cu trei:

    ThreadGroupgrup1=newThreadGroup("Producatori")Threadp1=newThread(grup,"Producator1")Threadp2=newThread(grup,"Producator2")

    ThreadGroupgrup2=newThreadGroup("Consumatori")Threadc1=newThread(grup,"Consumator1")Threadc2=newThread(grup,"Consumator2")Threadc3=newThread(grup,"Consumator3")

    Pentru a afla carui grup apartine un anumit fir de executie putem folosi metoda getThreadGroupa clasei Thread. Un grup poate avea ca parinte un alt grup, ceea ce nseamna ca firele de executie pot fi plasate ntroierarhiedegrupuri,ncareradacinaestegrupulimplicitmain,canfigurademaijos:

  • Exemplu:listareafirelordeexecutieactivepublicclassEnumerateTest{

    publicvoidlistCurrentThreads(){ThreadGroupcurrentGroup=Thread.currentThread().getThreadGroup()

    //aflunumarulfirelordeexecutieactiveintnumThreads=currentGroup.activeCount()

    //punintrunvectorreferintelafireledeexec.activeThread[]listOfThreads=newThread[numThreads]currentGroup.enumerate(listOfThreads)

    //leafisezpeecranfor(inti=0i

  • pr.connect(pw)//echivalentcupw.connect(pr)

    Scriereasicitireape/depecanaleserealizeazaprinmetodeleuzualereadsiwritentoateformelelor.Sareconsideramacumexemplulproducator/consumatorfolosindcanaledecomunicatie.Producatorul trimite datele printrun flux de iesire de tip DataOutputStreamcatre consumator care le primeste printrun flux de intrare de tip DataInputStream. Aceste doua fluxuri sunt interconectate prin intermediulunorfluxuridetip"pipe".importjava.io.*//clasaprincipalapublicclassTestPipes{

    publicstaticvoidmain(String[]args)throwsIOException{

    PipedOutputStreampipeOut=newPipedOutputStream()PipedInputStreampipeIn=newPipedInputStream(pipeOut)

    DataOutputStreamout=newDataOutputStream(pipeOut)DataInputStreamin=newDataInputStream(pipeIn)Producatorp1=newProducator(out)Consumatorc1=newConsumator(in)

    p1.start()c1.start()

    }}classProducatorextendsThread{

    privateDataOutputStreamout

    publicProducator(DataOutputStreamout){this.out=out

    }

    publicvoidrun(){for(inti=0i

  • catch(IOExceptione){} System.out.println("Consumatorulaprimit:\t"+value)

    }}

    }

    SincronizareafirelordeexecutiePna acum am vazut cum putem crea fire de executie independente si asincrone, cu alte cuvinte care

    nu depind n nici un fel de executia sau de rezultatele altor fire de executie. Exista nsa numeroase situatii cnd fire de executie separate, dar care ruleaza concurent, trebuie sa comunice ntre ele pentru a accesa diferite resurse comune sau pentru asi transmite dinamic rezultatele "muncii" lor. Cel mai elocvent scenariu n care firele de executie trebuie sa se comunice ntre ele este cunoscut sub numele de problema producatorului/consumatorului, n care producatorul genereaza un flux de date care este preluat si prelucrat decatreconsumator.

    Sa consideram de exemplu o aplicatie Java n care un fir de executie (producatorul) scrie date ntrun fisier n timp ce alt fir de executie (consumatorul) citeste date din acelasi fisier pentru a le prelucra. Sau, sa presupunem ca producatorul genereaza niste numere si le plaseaza, pe rnd, ntrun buffer iar consumatorul citeste numerele din acel buffer pentru a le interpreta. In ambele cazuri avem dea face cu fire de executie concurente care folosesc o resursa comuna : un fisier, respectiv un vector si, din acest motiv, ele trebuie sincronizatentromanieracaresapermitadecurgereanormalaaactivitatiilor.

    Scenariulproducator/consumatorPentru a ntelege mai bine modalitatea de sincronizare a doua fire de executie sa implementam efectiv

    oproblemadetipproducator/consumator.Saconsideramurmatoareasituatie:

    Producstorul genereaza numerele ntregi de la 1 la 10, fiecare la un interval neregulat cuprins ntre 0 si 100 de milisecunde. Pe masura ce le genereaza ncearca sa le plaseze ntro zona de memorie (o variabila ntreaga)deundesafiecititedecatreconsumator.

    Consumatorul va prelua, pe rnd, numerele generate de catre producator si va afisa valoarea lor pe ecran.

    Pentru a fi accesibila ambelor fire de executie, vom ncapsula variabila ce va contine numerele generate ntrun obiect descris de clasa Buffersi care va avea doua metode put(pentru punerea unui numar nbuffer)siget(pentruobtinereanumaruluidinbuffer).FaraafolosiniciunmecanismdesincronizareclasaBufferarataastfel:

    classBuffer{

    privateintnumber=1

    publicintget(){returnnumber

    }

    publicvoidput(intnumber){this.number=number

  • }}Vom implementa acum clasele Producator si Consumator care vor descrie cele doua fire de executie.

    AmbelevoraveaoreferintacomunalaunobiectdetipBufferprinintermediulcaruiasicomunicavalorile.classProducatorextendsThread{

    privateBufferbufferpublicProducator(Bufferb){

    buffer=b}publicvoidrun(){

    for(inti=0i

  • Producatorulapus: 1Producatorulapus: 2Producatorulapus: 3Producatorulapus: 4Producatorulapus: 5Producatorulapus: 6Producatorulapus: 7Producatorulapus: 8Producatorulapus: 9

    Ambele fire de executie acceseaza resursa comuna, adica obiectul de tip Buffer, ntro maniera haotica siacestlucrusentmpladindou\motive:

    consumatorul nu asteapta nainte de a citi ca producatorul sa genereze un numar si va prelua de mai multeoriacelasinumar.

    producatorul nu asteapta consumatorul sa preia numarul generat nainte de a produce un altul, n felul acestaconsumatorulva"rata"cusigurantaunelenumere(ncazulnostruaproapepetoate).

    Problema care se ridica n acest moment este : cine trebuie sa se ocupe de sincronizarea celor doua fire deexecutie:claseleProducatorsiConsumatorsauresursacomunaBuffer?Raspunsul este: resursa comuna Buffer, deoarece ea trebuie sa permita sau nu accesul la continutul sau si nu firele de executie care o folosesc. In felul acesta efortul sincronizarii este transferat de la producator/consumatorlaunnivelmaijos,celalresurseicritice.Activitatile producatorului si consumatorului trebuie sincronizate la nivelul resursei comune n doua privinte:

    Cele doua fire de executie nu trebuie sa acceseze simultan bufferul acest lucru se realizeaza prin blocarea obiectului Buffer atunci cnd este accesat de un fir de executie, astfel nct nici nu alt fir de executiesanulmaipoateaccesa.(vezi"Blocareaunuiobiect").

    Cele doua fire de executie trebuie sa se coordoneze, adica producatorul trebuie sa gaseasca o modalitate de a "spune" consumatorului ca a plasat o valoare n buffer, iar consumatorul trebuie sa comunice producatorului ca a preluat aceasta valoare, pentru ca acesta sa poata genera o alta. Pentru a realiza aceasta comunicare, clasa Thread pune la dispozitie metodele wait, notify, notifyAll. (vezi "Metodelewait,notify,notifyAll").

    FolosindsincronizareaclasaBuffervaarataastfel:classBuffer{

    privateintnumber=1privatebooleanavailable=falsepublicsynchronizedintget(){

    while(!available){try{

    wait()//asteaptaproducatorulsapunaovaloare

    }catch(InterruptedExceptione){} }

    available=falsenotifyAll()returnnumber

    }publicsynchronizedvoidput(intnumber){

    while(available){try{

    wait()

  • //asteaptaconsumatorulsapreiavaloarea}catch(InterruptedExceptione){}

    }this.number=numberavailable=truenotifyAll()

    }}Rezultatulobtinutvaficelscontat:

    Producatorulapus: 0Consumatorulaprimit: 0Producatorulapus: 1Consumatorulaprimit: 1...Producatorulapus: 9Consumatorulaprimit: 9

    Blocareaunuiobiect(cuvntulcheiesynchronized)Definitie

    Un segment de cod ce gestioneaza o resursa comuna mai multor de fire de executie separate si concurente se numeste sectiune critica. In Java o sectiune critica poate fi un bloc de instructiuni sau ometoda.

    Controlul accesului ntro sectiune critica se face prin cuvntul cheie synchronized. Platforma Java asociaza un monitor fiecarui obiect al unui program ce contine sectiuni critice care necesita sincronizare. Acest monitor va indica daca resursa critica este accesata de vreun fir de executie sau este libera, cu alte cuvinte "monitorizeaza" o resursa critica. In cazul n care este accesata, va "pune un lacat" pe aceasta, astfel nct sa mpiedice accesul altor fire de executie la ea. In momentul cnd resursa este eliberata "lacatul" va fi eliminatpentruapermiteaccesulaltorfiredeexecutie.In exemplul tip producator/consumator de mai sus, sectiunile critice sunt metodele putsi getiar resursa citica comuna este obiectul buffer. Consumatorul nu trebuie sa acceseze bufferul cnd producatorul tocmai pune o valoare n el, iar producatorul nu trebuie sa modifice valoarea din buffer n momentul cnd aceastaestecititadecatreconsumator.

    publicsynchronizedintget(){

    ...}publicsynchronizedvoidput(intnumber){

    ...}

    Sa observam ca ambele metode au fost declarate cu modificatorul synchronized. Cu toate acestea sistemul asociaza un monitor unei instante a clasei Buffer si nu unei metode anume. In momentul n este apelata o metoda sincrona firul de executie care a facut apelul va bloca obiectul a carei metoda o acceseaza , ceea ce nseamna ca celelalte fire de executie nu vor mai putea accesa resursele critice, adica nu vor putea apela nici o metoda sincrona din acel obiect. Acesta este un lucru logic, deoarece mai multe sectiuni critice (metodesincrone)aleunuiobiectgestioneazadefaptosinguraresursacritica.In exemplul nostru, atunci cnd producatorul apeleaza metoda put pentru a scrie un numar, va bloca tot obiectul de tip Buffer, astfel ca firul de executie consumator nu va avea acces la cealalta metoda sincrona get,sireciproc.

    publicsynchronizedvoidput(intnumber){//bufferblocatdeproducator...

  • //bufferdeblocatdeproducator}publicsynchronizedintget(){

    //bufferblocatdeconsumator...//bufferdeblocatdeconsumator

    }

    Metodelewait,notifysinotifyAllObiectul de tip Buffer din exemplul are o variabila membra privata numita number, n care este

    memorat numarul pe care l comunica producatorul si din care l preia consumatorul. De asemenea, mai are o variabila privata logica availablecare ne da starea bufferului: daca are valoarea true nseamna ca producatorul a pus o valoare n buffer si consumatorul nu a preluato nca daca este false, consumatorul a preluatvaloareadinbufferdarproducatorulnuapusdeocamdataaltalaloc.Deci,laprimavederemetodeleclaseiBufferartrebuisaarateastfel:

    publicsynchronizedintget(){if(available){

    available=falsereturnnumber

    }}publicsynchronizedintput(intnumber){

    if(!available){available=true

    this.number=number}

    }Implementate ca mai sus cele doua metode nu vor functiona corect Acest lucru se ntmpla deoarece

    firele de executie, desi si sincronizeaza accesul la buffer, nu se "asteapta" unul pe celalalt. Situatiile n care metodele get si put nu fac nimic vor duce la "ratarea" unor numere de catre consumator. Asadar, cele doua firedeexecutietrebuiesaseastepteunulpecelalalt.

    publicsynchronizedintget(){while(!available){

    //nimicasteptcavariabilasadevinatrue}available=falsereturnnumber

    }publicsynchronizedintput(intnumber){

    while(available){//nimicasteptcavariabilasadevinafalse

    }available=truethis.number=number

    }Varianta de mai sus, desi pare corecta, nu este. Aceasta deoarece implementarea metodelor este

    "selfish" cele doua metode si asteapta in mod egoist conditia de terminare. Ca urmare, corectitudinea functionariivadepindedesistemuldeoperare,ceeacetrprezintaogresealadeprogramare.Punerea corecta a unui fir de executie n asteptare se realizeaza cu metoda waita clasei Thread, care are trei forme:

    voidwait()voidwait(longtimeout)

  • voidwait(longtimeout,longnanos)Dupa apelul metodei wait, firul de executie curent elibereaza monitorul asociat obiectului respectiv si

    asteaptacaunadinurmatoareleconditiisafiendeplinita:

    un alt fir de executie informeaza pe cei care "asteapta" la un anumit monitor sa se trezeasca acest lucruserealizeazaprintrunapelalmetodeinotifyAllsaunotify.

    perioadadeastepatarespecificataaexpirat.

    Metoda waitpoate produce exceptii de tipul InterruptedException, atunci cnd firul de executie care asteapta (este deci n starea Not Runnable) este ntrerupt din asteptare si trecut fortat n starea Runnable,desiconditiaasteptatanuerancandeplinita.Metoda notifyAllinformeaza toate firele de executie care sunt n asteptare la monitorul obiectului curent ndeplinireaconditieipecareoasteptatu.Metodanotifyinformeazadoarunsingurfirdeexecutie.Iatavariantelecorectealemetodelorgetsiput:

    publicsynchronizedintget(){while(!available){

    try{wait()//asteaptaproducatorulsapunaovaloare

    }catch(InterruptedExceptione){} }

    available=falsenotifyAll()returnnumber

    }publicsynchronizedvoidput(intnumber){

    while(available){try{

    wait()//asteaptaconsumatorulsapreiavaloarea

    }catch(InterruptedExceptione){} }

    this.number=numberavailable=truenotifyAll()

    }}