Sunteți pe pagina 1din 9

Grupareafirelordeexecutie

Gruparea 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.Grupareafirelordeexecutieserealizeazaprinintermediulclasei
ThreadGroup
.
Fiecare fir de executie Java este mebmru al unui grup, indiferent daca specificam explicit acest lucru.
Afilierea unui firde executielaunanumitgrupserealizeazalacreareasasidevinepermanenta,nsensulca
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 programJavasecreeazaautomatunobiect
de tip T

hreadGroup cu numele m

ain
, 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 deexecutiesiplasarealuintrungrup(altuldectcelimplicit)se realizeaa
prinurmatoriiconstructoriaiclasei
Thread
:
publicThread(ThreadGroupgroup,Runnabletarget)
publicThread(ThreadGroupgroup,Stringname)
publicThread(ThreadGroupgroup,Runnabletarget,Stringname)

Fiecare din acesti costructori creeaza un fir de executie, l initializeaza si l plaseaza ntrungrupspecificat
ca argument. In exemplul urmator vor fi create doua grupuri, primul cudoua firedeexecutieiaral 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
getThreadGroup a clasei
Thread
. Un grup poate avea ca parinte un alt grup, ceea ce nseamna ca firele de executie pot fi plasate
ntroierarhiedegrupuri,ncareradacinaestegrupulimplicit
main
,canfigurademaijos:

Exemplu:listareafirelordeexecutieactive
publicclassEnumerateTest{
publicvoidlistCurrentThreads(){
ThreadGroupcurrentGroup=Thread.currentThread().getThreadGroup()

//aflunumarulfirelordeexecutieactive
intnumThreads=currentGroup.activeCount()

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

//leafisezpeecran
for(inti=0i<numThreadsi++)
System.out.println("Thread#"+i+"="+
listOfThreads[i].getName())
}
}

Comunicareaprinfluxuridetip"pipe"
O modalitate deosebit de utila prin care doua fire de executie pot comunica este realizata prin intermediul
canalelordecomunicatii(pipes)
.Acesteasuntimplementateprinfluxuridescrisedeclasele
PipedReader,PipedWriter
pentrucaractere,respectiv
PipedOutputStream,PipedInputStream
pentruocteti

Constructoriiacestorclasesunt:

publicPipedReader()
publicPipedReader(PipedWriterpw)throwsIOException
publicPipedWriter()
publicPipedWriter(PipedReaderpr)throwsIOException

In cazul n care este folosit constructorul fara argument conectarea unui flux de intrare cu un fluxdeiesire
sefaceprinmetoda
connect
:
publicvoidconnect(PipedWriterpw)throwsIOException
publicvoidconnect(PipedReaderpr)throwsIOException,

Intruct fluxurile care sunt conectate printrun pipe trebuie sa execute simultan operatii de scriere/citire
folosirealorsevafacencadrulunorfiredeexecutie.
Functionarea obicetelor care instantiaza P

ipedWriter si P

ipedReader este asemanatoare cu a canalelor


UNIX (pipes). Fiecare capat al unui canal este utilizat dintrun fir de executie separat. La un capat al
pipelineului se scriu caractere, la celalalt se citesc. La citire,dacanusuntdatedisponibilefirulde executie
se va bloca. Se observa ca acesta este un comportament tipic producatorconsumator, firele de executie
comunicndprintruncanal.
Realizareaconexiuniisefaceastfel:
PipedWriterpw1=newPipedWriter()
PipedReaderpr1=newPipedReader(pw1)
sau
PipedReaderpr2=newPipedReader()
PipedWriterpw2=newPipedWriter(pr2)
sau
PipedReaderpr=newPipedReader()
PipedWriterpw=newPipedWirter()

pr.connect(pw)//echivalentcu
pw.connect(pr)

Scriereasicitireape/depecanaleserealizeazaprinmetodeleuzuale
read
si
write
ntoateformelelor.

Sareconsideramacumexemplulproducator/consumatorfolosindcanaledecomunicatie.
Producatorul trimite datele printrun flux de iesire de tip D

ataOutputStream catre consumator care le


primeste printrun flux de intrare de tip D

ataInputStream
. Aceste doua fluxuri sunt interconectate prin
intermediulunorfluxuridetip"pipe".
importjava.io.*

//clasaprincipala
publicclassTestPipes{
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<10i++){
try{out.writeInt(i)}
catch(IOExceptione){}
System.out.println("Producatorulapus:\t"+i)
try{
sleep((int)(Math.random()*100))
}catch(InterruptedExceptione){}
}
}
}

classConsumatorextendsThread{
privateDataInputStreamin

publicConsumator(DataInputStreamin){
this.in=in
}
publicvoidrun(){
intvalue=0
for(inti=0i<10i++){
try{value=in.readInt()}

catch(IOExceptione){}

System.out.println("Consumatorulaprimit:\t"+value)
}
}
}

Sincronizareafirelordeexecutie
Pna 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 comunesau pentruasitransmitedinamicrezultatele"muncii"lor.Celmaielocventscenariu
n care firele de executie trebuie sa se comunice ntre ele este cunoscut sub numele de problema
producatorului/consumatorului
, n care producatorul genereaza un fluxdedatecareestepreluatsiprelucrat
decatreconsumator.

Sa consideram de exemplu o aplicatie Java n care un fir de executie (producatorul)scriedatentrunfisier


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, pernd,ntrunbufferiarconsumatorul
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/consumator
Pentru a ntelege mai bine modalitatea de sincronizareadouafiredeexecutiesaimplementamefectiv
oproblemadetipproducator/consumator.
Saconsideramurmatoareasituatie:
Producstorul genereaza numerele ntregi dela1la10,fiecarelaunintervalneregulatcuprinsntre0si
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 obiectdescrisdeclasa
Buffersicare va aveadouametode
put(pentrupunereaunuinumar
nbuffer)si
get
(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.
class
Producator
extendsThread{
privateBufferbuffer
publicProducator(Bufferb){
buffer=b
}
publicvoidrun(){
for(inti=0i<10i++){
buffer.put(i)
System.out.println("Producatorulapus:\t"+i)
try{
sleep((int)(Math.random()*100))
}catch(InterruptedExceptione){}
}
}
}

class
Consumator
extendsThread{
privateBufferbuffer
publicConsumator(Bufferb){
buffer=b
}
publicvoidrun(){
intvalue=0
for(inti=0i<10i++){
value=buffer.get()
System.out.println("Consumatorulaprimit:\t"+value)
}
}
}
//Clasaprincipala
publicclassTestSincronizare1{
publicstaticvoidmain(String[]args){
Bufferb=newBuffer()
Producatorp1=newProducator(b)
Consumatorc1=newConsumator(b)
p1.start()
c1.start()
}
}

Dupa cum ne asteptam rezultatul rularii acestui program nu va rezolva fi nici pe departe problema
propusa de noi, motivul fiind lipsa oricarei sincronizari ntre cele doua fire de executie. Mai precis,
rezultatulvaficevadeforma:
Consumatorulaprimit:
Consumatorulaprimit:
Producatorulapus:
Consumatorulaprimit:
Consumatorulaprimit:
Consumatorulaprimit:
Consumatorulaprimit:
Consumatorulaprimit:
Consumatorulaprimit:
Consumatorulaprimit:
Consumatorulaprimit:

1
1
0
0
0
0
0
0
0
0
0

Producatorulapus:
Producatorulapus:
Producatorulapus:
Producatorulapus:
Producatorulapus:
Producatorulapus:
Producatorulapus:
Producatorulapus:
Producatorulapus:

1
2
3
4
5
6
7
8
9

Ambele fire de executieacceseazaresursacomuna,adicaobiectuldetipBuffer,ntromanierahaotica


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 aproduceunaltul,nfelul
acestaconsumatorulva"rata"cusigurantaunelenumere(ncazulnostruaproapepetoate).
Problema care seridicanacestmomenteste:cinetrebuiesaseocupedesincronizareacelordouafire
deexecutie:claseleProducatorsiConsumatorsauresursacomunaBuffer?
Raspunsul este: resursa comunaBuffer,deoareceeatrebuiesa permitasaunuaccesullacontinutulsausinu
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 w

ait,notify,notifyAll
. (vezi
"Metodelewait,notify,notifyAll").
FolosindsincronizareaclasaBuffervaarataastfel:
classBuffer{
privateintnumber=1
privatebooleanavailable=false
public
synchronized
intget(){
while(!available){
try{
wait()

//asteaptaproducatorulsapunaovaloare
}catch(InterruptedExceptione){}

}
available=false
notifyAll
()
returnnumber
}
public
synchronized
voidput(intnumber){
while(available){
try{
wait()

//asteaptaconsumatorulsapreiavaloarea
}catch(InterruptedExceptione){}
}
this.number=number
available=true
notifyAll
()

Rezultatulobtinutvaficelscontat:
Producatorulapus:
Consumatorulaprimit:
Producatorulapus:
Consumatorulaprimit:
...
Producatorulapus:
Consumatorulaprimit:

0
0
1
1
9
9

Blocareaunuiobiect(cuvntulcheiesynchronized)
Definitie
Un segment de cod ce gestioneaza o resursa comunamaimultordefiredeexecutieseparatesi
concurente se numeste
sectiune critica
. In Java o sectiune criticapoatefiunblocdeinstructiunisau
ometoda.
Controlul accesului ntro sectiune critica se face prin cuvntul cheie s

ynchronized
. 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 cazulncareesteaccesata,va"puneunlacat"peaceasta,astfel
nct sa mpiedice accesul altor firedeexecutie laea.Inmomentulcndresursaesteeliberata"lacatul"vafi
eliminatpentruapermiteaccesulaltorfiredeexecutie.
In exemplul tip producator/consumator de mai sus, sectiunile critice sunt metodele p

ut si g

et iar resursa
citica comuna este obiectul b

uffer
. 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.

public
synchronized
intget(){
...
}
public
synchronized
voidput(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 sincronafiruldeexecutiecareafacutapelulvablocaobiectulacareimetodaoacceseaza,
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 lucrulogic,deoarecemaimultesectiunicritice
(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,notifysinotifyAll
Obiectul 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 lpreiaconsumatorul.Deasemenea,maiare
o variabila privata logica
available care 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=false
returnnumber
}
}
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 labuffer,nuse"asteapta"unulpecelalalt.Situatiilencare
metodele get si put nu fac nimic vor duce la "ratarea" unor numere de catreconsumator.Asadar,celedoua
firedeexecutietrebuiesaseastepteunulpecelalalt.
publicsynchronizedintget(){
while(!available){
//nimicasteptcavariabilasadevinatrue
}
available=false
returnnumber
}
publicsynchronizedintput(intnumber){
while(available){
//nimicasteptcavariabilasadevinafalse
}
available=true
this.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.

Punereacorectaaunuifirdeexecutienasteptareserealizeazacumetoda w

aitaclaseiT

hread
,carearetrei
forme:
voidwait()
voidwait(longtimeout)

voidwait(longtimeout,longnanos)
Dupa apelul metodei
wait,
firul de executiecurentelibereazamonitorulasociatobiectuluirespectiv si

asteaptacaunadinurmatoareleconditiisafiendeplinita:

un alt fir de executie informeaza pe cei care "asteapta" la un anumit monitor sa se trezeasca acest
lucruserealizeazaprintrunapelalmetodei
notifyAll
sau
notify
.
perioadadeastepatarespecificataaexpirat.
Metoda w

ait poate produce exceptii de tipul I

nterruptedException
, 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 n

otifyAll informeaza toate firele de executie care sunt n asteptare la monitorul obiectului curent
ndeplinireaconditieipecareoasteptatu.Metoda
notify
informeazadoarunsingurfirdeexecutie.

Iatavariantelecorectealemetodelor
get
si
put
:
publicsynchronizedintget(){
while(!available){
try{
wait()
//asteaptaproducatorulsapunaovaloare
}catch(InterruptedExceptione){}

}
available=false
notifyAll()
returnnumber
}
publicsynchronizedvoidput(intnumber){
while(available){
try{
wait()
//asteaptaconsumatorulsapreiavaloarea
}catch(InterruptedExceptione){}

}
this.number=number
available=true
notifyAll()
}
}

S-ar putea să vă placă și