Sunteți pe pagina 1din 30

Curs 8

Bazele multithreading.......................................................................................................................... 2
Clasa Thread i interfaa Runnable....................................................................................................... 4
Crearea unui fir de execuie ................................................................................................................. 4
Interfaa Runnable ........................................................................................................................... 4
mbuntiri aduse exemplului ........................................................................................................ 7
Motenirea unui Thread .................................................................................................................. 9
Crearea mai multor fire de execuie................................................................................................... 11
Cnd se ncheie un fir de execuie? ................................................................................................ 14
Prioriti n fire de execuie ............................................................................................................... 16
Sincronizarea ..................................................................................................................................... 18
Folosirea metodelor sincronizate ................................................................................................... 19
Blocul synchronized ....................................................................................................................... 21
Comunicarea ntre fire de execuie .................................................................................................... 22
Suspendarea, reluarea i oprirea firelor de execuie .......................................................................... 26
Grupuri de fire de execuie ................................................................................................................ 29
Alte metode................................................................................................................................... 30

Bazele multithreading
Exist doua tipuri distincte de multitasking: unul bazat pe procese i al doilea bazat pe fire de
execuie sau thread-uri. Este important s facem distincia dintre cele dou. Un proces este, n esen,
un program ce se afl n execuie. De aceea multitasking bazat pe procese nseamn faptul c dou sau
mai multe programe ruleaz n acelai timp. De exemplu, compilatorul Java va permite crearea unor noi
programe, n timp ce folosii un editor de text, sau navigai pe Internet.
n cazul multitasking bazat pe thread-uri, acesta este cea mai mic unitate ce va fi programat
de dispecer. Aceasta nseamn c un program poate realiza mai multe lucruri simultan. De exemplu
aplicaia de mail Outlook, sau un client de mail va permite si trimiterea/primirea de mail-uri in timp ce
compunei un nou mail, sau verificai lista de prioriti dintr-o zi, sau cutai un anumit mail, etc.
Avantajul principal al multithreading este c permite scrierea unor programe foarte eficiente,
deoarece se elimin spaiile inutile temporale prezente n multe programe, atunci cnd acesta este idle.
Majoritatea dispozitivelor I/O sunt mai lente dect CPU. De aceea programul va petrece majoritatea
execuiei ateptnd informaia de la aceste dispozitive. n acest timp, n cazul folosirii multithreading, se
pot executa instruciuni care s realizeze alte sarcini. De exemplu, n timp ce un program trimite un fiier
pe Internet, alt parte a programului poate citi de la tastatura, urmtoarea bucat ce va fi trimis.
Un fir de execuie este o parte dintr-un proces executat secvenial, o serie de instruciuni ce vor
fi executate secvenial. Folosirea a dou sau mai multe thread-uri duce la execuia n paralel a acestor
serii de instruciuni n cadrul unui proces. Mai jos avem reprezentat modul de funcionare al unui proces
pe un singur thread.

Figura 1. Un proces cu un singur fir de execuie


Adevrata utilitate a unui thread intervine n cazul n care mai multe fire de execuie coexist n
acelai proces.

Figura 2. Un proces cu dou fire de execuie

Un fir de execuie se poate afla n mai multe stri. Poate fi running, adic n execuie. Poate fi
ready to run, n clipa n care dispecerul i va asigura timpul n CPU. Un thread aflat n execuie poate fi
suspendat, ceea ce nseamn c este oprit pentru o perioad. Poate fi reluat, mai trziu, adic va intra n
starea de resumed. Un thread poate fi blocat n momentul cnd ateapt o resurs. Un thread poate fi
terminat atunci cnd execuia se ncheie i nu va mai fi reluat.
Figura de mai jos reprezint strile unui fir de execuie.

Figura 3. Strile de execuie ale unui thread


Pe lng aceste caracteristici, mai este nevoie de o anumit funcionalitate, numit sincronizare,
ce permite execuia firelor ntr-o manier controlat.
Fa de modul n care limbajul C trateaz aceast problem a multithreading-ului , Java ascunde unele
detalii , tocmai pentru a nlesni i a face ct mai convenient lucrul cu thread-uri.

Clasa Thread i interfaa Runnable


n Java, sistemul multithreading este construit pe clasa Thread i interfaa Runnable. Pentru a
crea un nou fir de execuie, va trebui s extindem clasa Thread sau s implementm interfaa Runnable.
Clasa Thread definete cteva metode care ajut la tratarea situaiilor ce intervin n lucrul cu
thread-uri. Iat cele mai folosite:
Medoda

Descriere

final String getName( )


final int getPriority( )
final boolean isAlive( )
final void join( )
void run( )
static void sleep(long milliseconds)
void start( )

Se obine numele thread-ului


Se obine prioritatea thread-ului
Determin faptul c un thread mai este n execuie sau nu.
Ateapt terminarea unui thread pentru a continua.
Entry point pentru un thread.
Suspend un thread un numr de milisecunde
ncepe execuia unui fir prin apel run()

Toate procesele au cel puin un fir de execuie ce se numete firul principal sau main thread.
Acesta se va executa nainte celorlalte eventual fire de execuie ce vor fi create ulterior.

Crearea unui fir de execuie


Un fir de execuie se poate crea prin instanierea unui obiect de tip Thread. Clasa Thread
ncapsuleaz un obiect ce implementeaz interfaa Runnable. Exist dou moduri de a crea un fir:
1. Implementarea interfeei Runnable
2. Extinderea (motenirea) clasei Thread
Interfaa Runnable
Aceast interfa definete doar o metod numit run() declarat astfel:
public void run()

n cadrul acestei metode, vei defini codul ce constituie noul fir de execuie. Metoda run() poate
apela alte metode, folosi alte clase, i declara variabile ca orice metod. Diferena major este c run()
reprezint entry point pentru un nou thread din cadrul procesului. Acest thread i ncheie execuia
atunci cnd se iese din funcia run (cu return).
Modul de lucru este urmtorul:
1. Se creeaz o clas ce implementeaz interfaa Runnable.
2. Se instaniaz un obiect de tip Thread, ntr-una din metodele din acea clas ( chiar si in
constructorul clasei ). Constructorul unui Thread va fi:

Thread(Runnable threadOb)

3. Odat creat, noul obiect de tip Thread, nu va porni pn cnd nu apelm metoda start(). n
principiu apelul metodei start() nseamn apelul metodei run(). Apelul metodei start este:
void start()

Mai jos avem un exemplu de folosirea a unui nou fir de execute:


// crearea unui fir de execute
//prin implementarea interfeei Runnable
class MyThread implements Runnable
{
int count;
String threadName;
MyThread(String name)
{
count = 0;
threadName = name;
}
// Entry point al firului
public void run()
{
System.out.println(threadName + " starting.");
try
{
do
{
//suspend thread-ul curent
Thread.sleep(500);
System.out.println("In " + threadName +
", count is " + count);
count++;
} while(count < 10);
}
catch(InterruptedException exc)
{
System.out.println(threadName + " interrupted.");
}
System.out.println(threadName + " terminating.");
}
}
class DemoThread
{
public static void main(String args[])
{
System.out.println("Main thread starting.");
// mai nti se construiete obiectul
// ce va conine thread-ul
MyThread mt = new MyThread("Child #1");
// se construiete un fir pe baza obiectului
Thread newThrd = new Thread(mt);
// incepe noul fir de execute
newThrd.start();
do

System.out.print(".");
try
{
Thread.sleep(100);
}
catch(InterruptedException exc)
{
System.out.println("Main thread interrupted.");
}
} while (mt.count != 10);
System.out.println("Main thread ending.");

Prima dat, clasa MyThread implementeaz Runnable. Aceasta nseamn c un obiect de tipul
MyThread va fi folosit pentru a crea un thread i deci, va fi parametrul unui constructor Thread.
n interiorul metodei run(), exist o bucl while, ce contorizeaz de la 0 la 9. Metoda
Thread.sleep(500); are ca scop, suspendarea firului de execuie curent timp de 500 de
milisecunde.
n clasa DemoThread, n metoda main(), se creeaz un nou obiect, de tip MyThread care va fi
apelat ulterior de newThrd:
MyThread mt = new MyThread("Child #1");
Thread newThrd = new Thread(mt);
// incepe noul fir de execute
newThrd.start();

Obiectul mt este folosit pentru a crea un Thread, iar acest lucru este posibil pentru c MyThread
implementeaz Runnable . Execuia unui thread ncepe cu start. Pe firul principal de execuie,
instruciunile vor fi rulate ca i cnd start() ar fi o metod obinuit. Firul principal de execuie va rula o
bucl while n care ateapt ca mt.count s ajung la 10. Acest lucru se va ntmpla, datorit faptului
c n thread-ul secundar count crete.
Iat rezultatul rulrii acestui program:
Main thread starting.
.Child #1 starting.
....In Child #1, count is 0
.....In Child #1, count is 1
.....In Child #1, count is 2
.....In Child #1, count is 3
.....In Child #1, count is 4
.....In Child #1, count is 5
.....In Child #1, count is 6
.....In Child #1, count is 7
.....In Child #1, count is 8
.....In Child #1, count is 9
Child #1 terminating.
Main thread ending.

O diagrama a acestui program ar fi urmtoarea:

Figura 4. Diagrama execuiei unui fir

mbuntiri aduse exemplului


Programul anterior este valid, ns poate fi optimizat:
1. Putem avea un thread care s nceap execuia la instanierea obiectului. n cazul MyThread
aceasta se face instaniind un obiect de tip Thread n constructorul lui MyThread.
2. Nu mai este nevoie ca MyThread s aib un obiect de tip Thread cu un nume, deoarece
modificm numele la instanierea noului Thread. Pentru aceasta avem urmtorul
constructor:
Thread(Runnable threadOb, String name)

Se poate obine ulterior, numele Thread-ului prin apelul metodei getName:


final String getName( )

Setarea numelui unui thread, dup ce acesta a fost creat, se face cu setName():
final void setName(String threadName)

Iat mai jos o versiune a programului anterior, cu mbuntirile specificate:


// crearea unui fir de execute
//prin implementarea interfeei Runnable
class MyThread implements Runnable
{
int count;
//firul de execute cu care vom lucra
Thread thread;
MyThread(String name)
{
count = 0;
//instaniem un nor fir
thread = new Thread(this, name);
//firul va porni la apelul constructorului
//adic la instanierea lui MyThread
thread.start();
}
// Entry point al firului
public void run()
{
System.out.println(thread.getName() + " starting.");
try
{
do
{
//suspend thread-ul curent
Thread.sleep(500);
System.out.println("In " + thread.getName() +
", count is " + count);
count++;
} while(count < 10);
}
catch(InterruptedException exc)
{
System.out.println(thread.getName() + " interrupted.");
}
System.out.println(thread.getName() + " terminating.");
}
}
class DemoThread
{
public static void main(String args[])
{
System.out.println("Main thread starting.");
//acum thread-ul nou va porni automat
//la instanierea lui mt
MyThread mt = new MyThread("Child #1");

do
{
System.out.print(".");
try
{
Thread.sleep(100);
}
catch(InterruptedException exc)
{
System.out.println("Main thread interrupted.");
}
} while (mt.count != 10);
System.out.println("Main thread ending.");
}
}

Modificrile majore sunt: mutarea obiectului de tip Thread n cadrul clasei MyThread i pornirea
acestui fir din constructorul clasei MyThread.
Motenirea unui Thread

Implementarea interfeei Runnable este un mod de a crea o clas ce poate instania fire. A doua
ar fi extinderea unei clase numit Thread. Atunci cnd o clas extinde Thread, va trebui s suprascrie
metoda run(), care este un entry point pentru noul fir. De asemenea trebuie s apeleze start() pentru
ca noul fir s i nceap execuia.
Pornind de la exemplul anterior, putem transforma acesta aplicnd motenirea lui Thread:
1. Se schimba declaraia lui MyThread ca s extind Thread:
class MyThread extends Thread
{
.

2. Se terge aceast declaraie, nu mai avem nevoie de ea:


Thread thread;

Variabila thread nu mai are nici o utilitate, din moment ce MyThread este un Thread.
3. Se schimb constructorul lui MyThread ca s arate astfel:
// construirea unui fir de execuie.
MyThread(String name)
{

super(name); // apelez constructor cu nume


count = 0;
start(); // ncep execuia
}

Apelul lui super(name); este n fapt, apelul unui Thread cu parametru un String:
Thread(String name);

4. Se schimb run() astfel nct acum apeleaz getName().


Dup aceste modificri programul arat astfel:
class MyThread extends Thread
{
int count;
//firul de execuie cu care vom lucra
Thread thread;
MyThread(String name)
{
super(name);
count = 0;
this.start();
}
// Entry point al firului
public void run()
{
System.out.println(getName() + " starting.");
try
{
do
{
//suspend thread-ul curent
Thread.sleep(500);
System.out.println("In " + getName() +
", count is " + count);
count++;
} while(count < 10);
}
catch(InterruptedException exc)
{
System.out.println(getName() + " interrupted.");
}
System.out.println(getName() + " terminating.");
}
}

Clasa DemoThread nu sufer nici un fel de modificri fa de programul precedent.

Crearea mai multor fire de execuie


Exemplele anterioare conineau doar un singur fir de execuie. Totui, programul poate crea
oricte astfel de fire. Urmtorul program creeaz trei thread-uri:
class MyThread implements Runnable
{
int count;
Thread thread;
// Constructorul ce creeaz un nou thread
MyThread(String name)
{
thread = new Thread(this, name);
count = 0;
thread.start(); // incepe execuia
}
//entry point in firul de execuie
public void run()
{
System.out.println(thread.getName() + " starting.");
try
{
// se mrete contorul
do
{
Thread.sleep(500);
System.out.println("In " + thread.getName() +
", count is " + count);
count++;
} while(count < 10);
}
catch(InterruptedException exc)
{
System.out.println(thread.getName() + " interrupted.");
}
System.out.println(thread.getName() + " terminating.");
}
}
class ThreeChildThreads {
public static void main(String args[])
{
System.out.println("Main thread starting.");
//cream trei fire de execuie
MyThread mt1 = new MyThread("Child #1");
MyThread mt2 = new MyThread("Child #2");
MyThread mt3 = new MyThread("Child #3");
//care vor fi automat lansate
//in bucla while, thread-ul principal

//ateapt ca toate firele sa se ncheie


do
{
System.out.print(".");
try
{
Thread.sleep(100);
}
catch(InterruptedException exc)
{
System.out.println("Main thread interrupted.");
}
} while (mt1.count < 10 || mt2.count < 10 || mt3.count < 10);
System.out.println("Main thread ending.");
}
}

Cele trei fire de execuie vor rula n paralel cu firul principal, acesta ateptnd ca cele trei sa i ncheie
execuia. O diagram a acestei funcionaliti este n figura de mai jos:

Figura 5. Diagrama execuiei pe mai multe fire

Rezultatul rulrii acestui program este:


Main thread starting.
Child #1 starting.
.Child #2 starting.
Child #3 starting.
....In Child #1, count is 0
In Child #2, count is 0
In Child #3, count is 0
.....In Child #1, count is 1
In Child #3, count is 1
In Child #2, count is 1
.....In Child #1, count is 2
In Child #2, count is 2
In Child #3, count is 2
.....In Child #1, count is 3
In Child #2, count is 3
In Child #3, count is 3
.....In Child #1, count is 4
In Child #2, count is 4
In Child #3, count is 4
.....In Child #1, count is 5
In Child #2, count is 5
In Child #3, count is 5
.....In Child #1, count is 6
In Child #2, count is 6
In Child #3, count is 6
.....In Child #1, count is 7
In Child #3, count is 7
In Child #2, count is 7
.....In Child #1, count is 8
In Child #3, count is 8
In Child #2, count is 8
.....In Child #1, count is 9
Child #1 terminating.
In Child #3, count is 9
Child #3 terminating.
In Child #2, count is 9
Child #2 terminating.
Main thread ending.

Acest rezultat depinde de sistemul pe care este rulat i poate diferi.


ntrebarea fireasc este de ce avem nevoie de dou moduri de a crea fire de execuie ( unul prin
extinderea Thread i altul prin implementarea Runnable ) i care este mai performant?
Rspunsul este c o clas Thread definete o serie de metode ce pot fi suprascrise de clasa ce
deriv din Thread. Singura care se impune a fi suprascris este run(). Aceasta este i condiia atunci cnd
clasa implementeaz interfaa Runnable. Aa c dac nu se dorete suprascrierea altor metode din
Thread, atunci este mai bine s folosim a doua metod, pentru simplitate.

Cnd se ncheie un fir de execuie?


Este util s tim cnd un fir de execuie s-a ncheiat, pentru a controla logica i fluxul
programului. n exemplele anterioare, acest lucru a fost posibil datorit unei variabile de control i
anume count. Aceasta este o soluie slab din punct de vedere tehnic. Clasa Thread ofer dou moduri
de a determina faptul c un fir de execuie s-a ncheiat. Primul este prin funcia isAllive():
final boolean isAllive()

Aceast metod returneaz true, dac firul de execuie, pentru care a fost apelat, nc ruleaz,
i false altfel. Pentru a verifica funcionalitatea isAlive(), vom modifica versiunea clasei
ThreeChildThreads.
class ThreeChildThreads {
public static void main(String args[])
{
System.out.println("Main thread starting.");
//cream trei fire de execuie
MyThread mt1 = new MyThread("Child #1");
MyThread mt2 = new MyThread("Child #2");
MyThread mt3 = new MyThread("Child #3");
//care vor fi automat lansate
//in bucla while, thread-ul principal
//ateapt ca toate firele sa se ncheie
do
{
System.out.print(".");
try
{
Thread.sleep(100);
}
catch(InterruptedException exc)
{
System.out.println("Main thread interrupted.");
}
} while (mt1.thread.isAlive() || mt2.thread.isAlive() ||
mt3.thread.isAlive() );
System.out.println("Main thread ending.");
}
}

Dup cum se poate observa, acest program este la fel ca i cel anterior, cu o modificare: condiia
din while se bazeaz pe aceast funcie, oferind robustee aplicaiei.
Cel de-al doilea mod de a atepta ca un fir s i ncheie execuia este join():
final void join() throws IntrerruptedException

Aceast metod va impune ateptarea terminarea firului pentru care a fost apelat. Numele
provine de la un concept de crearea a unui thread, lansarea a acestuia i ateptarea terminrii lui pentru
ca cel care a creat firul s se uneasc cu acesta. Exist forme adiionale ale acestei funcii, prin care se
permite specificarea unui numr de milisecunde, sau nanosecunde, timp de ateptare ca acel fir s se
ncheie. Mai jos este exemplul pentru aceast funcie:
class ThreeChildThreads
{
public static void main(String args[])
{
System.out.println("Main thread starting.");
//cream trei fire de execuie
MyThread mt1 = new MyThread("Child #1");
MyThread mt2 = new MyThread("Child #2");
MyThread mt3 = new MyThread("Child #3");
//care vor fi automat lansate
//in bucla while, thread-ul principal
//ateapt ca toate firele sa se ncheie
try
{
mt1.thread.join();
System.out.println("Child 1 joined.");
mt2.thread.join();
System.out.println("Child 2 joined.");
mt3.thread.join();
System.out.println("Child 3 joined.");
}
catch(InterruptedException exc)
{
System.out.println("Main thread interrupted.");
}
System.out.println("Main thread ending.");
}
}

Ateptarea ca un fir de execuie s se ncheie are loc prin apelurile de tipul:


mt1.thread.join();

Prioriti n fire de execuie


Fiecare fir de execuie are asociat o anumit prioritate. Aceasta este determinat de ct timp n
CPU este alocat acelui thread. n general prioritile joase denot puin timp, iar cele mari denot mai
mult timp n CPU. Evident, ct de mult timp va avea acces la CPU, un fir de execuie, influeneaz direct,
fluxul logic al programului.
Este important s nelegem factorii care influeneaz accesul la CPU. De exemplu, dac un fir cu
prioritate mare ateapt o resurs auxiliar, atunci el va fi blocat, i alt fir cu o prioritate mai mic va
avea acces. Totui odat ce thread-ul cu prioritatea mai mare are resursa, va opri execuia firului cu
prioritate mai mic pentru ai relua propria execuie. Alt factor care poate afecta dispecerul de fire de
execuie, este modul n care sistemul de operare implementeaz multitasking. De aceea, doar prin
asignarea de prioriti mai mici, mai mari, nu va influena neaprat rapiditatea cu care un fir ruleaz.
Prioritatea nseamn un acces probabil mai mic sau mai mare la CPU.
Atunci cnd un fir de execuie pornete, prioritatea sa este egal cu cea a thread-ului printe.
Valoarea nivelului de prioritate trebuie s fie ntre MIN_PRIORITY i MAX_PRIORITY. n mod normal,
aceste valori sunt 1 i 10. Pentru a returna un fir cu prioritate implicit, avem NORM_PRIORITY ce
nseamn 5. Aceste valori sunt constante n clasa Thread. Se poate obine prioritatea actual a unui fior
de execuie apelnd metoda getPriority():
final int getPriority()

Exemplul urmtor demonstreaz folosirea a dou fire de prioritate diferit. Metoda run()
conine o bulc while care contorizeaz iteraii. Aceast bulc se oprete cnd numrul de iteraii
depete 1000000 sau variabila stop a fost setat pe true. Iniial stop este setat pe false, dar primul
thread care termin de iterat, o va seta pe true. Evident aceasta va face ca i al doilea thread s ncheie
bucla. La fiecare parcurgere a buclei, variabila string, currentName, este comparat cu firul aflat n
execuie. Dac este diferit, se realizeaz modificarea acestei variabile. Aceasta permite vizualizarea
accesrii CPU a unui anumit thread.
class Priority implements Runnable
{
int count;
Thread thread;
static boolean stop = false;
static String currentName;
//Constructorul unui nou fir
//acesta nu pornete thread-ul
Priority(String name)
{
thread = new Thread(this, name);
count = 0;
currentName = name;
}
// incepe execuia unui nou fir
public void run()
{
System.out.println(thread.getName() + " starting.");

do
{
count++;
if(currentName.compareTo(thread.getName()) != 0)
{
currentName = thread.getName();
System.out.println("In " + currentName);
}
} while(stop == false && count < 10000000);
stop = true;
System.out.println("\n" + thread.getName() +
" terminating.");
}
}
class PriorityDemo
{
public static void main(String args[])
{
Priority mt1 = new Priority("High Priority");
Priority mt2 = new Priority("Low Priority");
// mt1 primete o prioritate mai mare dect mt2
mt1.thread.setPriority(Thread.NORM_PRIORITY+2);
mt2.thread.setPriority(Thread.NORM_PRIORITY-2);
//pornesc ambele tread-uri
mt1.thread.start();
mt2.thread.start();
//ateptarea terminrii ambelor thread-uri
try
{
mt1.thread.join();
mt2.thread.join();
}
catch(InterruptedException exc)
{
System.out.println("Main thread interrupted.");
}
System.out.println("\nHigh priority thread counted to " +
mt1.count);
System.out.println("Low priority thread counted to " +
mt2.count);
}
}

Efectul rulrii acestui program este:


High Priority starting.
In High Priority
Low Priority starting.
In Low Priority In High Priority
In Low Priority
In Low Priority
In High Priority

In High Priority
In High Priority
In Low Priority
In High Priority
In High Priority
In Low Priority
In High Priority
High Priority terminating.
Low Priority terminating.
High priority thread counted to 10000000
Low priority thread counted to 1194749

n acest mod putem observa c pentru acest exemplu, thread-ul cu prioritate mai mare a avut
majoritatea timpului CPU. Acest rezultat va depinde de sistemul de operare i maina pe care va rula.

Sincronizarea
Atunci cnd folosim mai multe fire de execuie, este necesar uneori, s coordonm fluxul logic,
acest proces numindu-se sincronizare. Cel mai simplu motiv pentru aceast sincronizare, este ca dou
sau mai multe fire s aib acces la o resurs comun, dar doar un singur fir s acceseze la un moment
dat acea resurs. De exemplu scrierea ntr-un fiier, efectuat din dou fire de execuie trebuie
controlat. Aceasta se realizeaz astfel: un fir este pus n stare de ateptare pn cnd firul care are
acces la resurs termin aciunea, urmnd ca firul suspendat s i reia execuia.
Sincronizarea n Java este realizat prin intermediul conceptului de monitor, ce controleaz
accesul la un obiect. Monitorul funcioneaz implementnd conceptul de blocare. Cnd un obiect este
blocat de un fir de execuie, nici un alt fir de execuie nu are acces la acel obiect. Cnd firul de execuie
elibereaz acel lock, obiectul devine disponibil pentru celelalte fire de execuie.
Toate obiectele n Java au un monitor. Aceast caracteristic este implementat n limbaj. De
aceea toate obiectele pot fi sincronizate. Acest lucru se va realiza prin cuvntul cheie synchronized i alte
cteva metode pe care toate obiectele le au. Exist dou metode prin care se poate sincroniza fluxul
logic al unui program.

Folosirea metodelor sincronizate


Se poate sincroniza accesul la o metod folosind cuvntul cheie synchronized. Atunci cnd acea
metod este apelat , firul de execuie n care s-a fcut apelul intr n obiectul monitor, care blocheaz
obiectul. n timpul blocrii, nici un alt thread nu poate intra n metod, sau accesa orice metod
sincronizat. Atunci cnd firul de execuie revine din metod, se realizeaz deblocarea.
Urmtorul program demonstreaz folosirea acestei metode de sincronizare:
//clasa ce calculeaz o sum de elemente
//si conine metoda sincronizata
class SumArray
{
private int sum;
//metoda are specificatorul de sincronizare
synchronized int sumArray(int nums[])
{
sum = 0; // resetarea sumei
for(int i=0; i<nums.length; i++)
{
sum += nums[i];
System.out.println("Running total for " +
Thread.currentThread().getName() +
" is " + sum);
try
{
Thread.sleep(15); //timp in care
//se poate schimba de la un fir la altul
}
catch(InterruptedException exc)
{
System.out.println("Main thread interrupted.");
}
}
return sum;
}
}
//clasa care se ocupa de thread
class MyThread implements Runnable
{
Thread thread;
static SumArray sa = new SumArray();
int a[];
int answer;
// Constructorul unui nou fir
MyThread(String name, int nums[])
{
thread = new Thread(this, name);
a = nums;

thread.start(); //pornesc thread-ul


}
public void run()//incepe execuia noului fir
{
int sum;
System.out.println(thread.getName() + " starting.");
answer = sa.sumArray(a);
System.out.println("Sum for " + thread.getName() +
" is " + answer);
System.out.println(thread.getName() + " terminating.");
}
}
class SyncDemo
{
public static void main(String args[])
{
int a[] = {1, 2, 3, 4, 5};
MyThread mt1 = new MyThread("Child #1", a);
MyThread mt2 = new MyThread("Child #2", a);
}
}

Acest exemplu este compus din trei clase. Prima i anume SumArray conine metoda
sumArray ce realizeaz nsumarea elementelor dintr-un ir. Acest ir este pasat ca parametru al
metodei. A doua clas este, MyThread cea care se ocup de firul de execuie propriu zis. Firul de
execuie va apela metoda sumArray prin intermediul obiectului sa de tip SumArray. Acesta este
declarat static n clasa MyThread. Pe metoda de run() a firului de execuie se va rula aceast metoda de
nsumare a elementelor unui ir. n clasa SyncDemo, n metoda main() este creat un ir i anume a, i

dou fire de execuie ce au ca parametru acelai ir. Cu alte cuvinte, aceeai metod
sa.sumArray(a); va fi apelat n ambele fire, crend o concuren de acces la suma rezultat. Suma
nu va fi ns afectat, deoarece firele vor executa succesiv metoda sumArray .
Rezultatul este:
Child #1 starting.
Running total for Child
Child #2 starting.
Running total for Child
Running total for Child
Running total for Child
Running total for Child
Running total for Child
Sum for Child #1 is 15
Child #1 terminating.
Running total for Child
Running total for Child
Running total for Child
Running total for Child
Sum for Child #2 is 15
Child #2 terminating.

#1 is 1
#1
#1
#1
#1
#2

is
is
is
is
is

3
6
10
15
1

#2
#2
#2
#2

is
is
is
is

3
6
10
15

Aceasta nseamn c execuia a avut loc conform ateptrilor i sumele calculate sunt cele
prevzute. Dar dac eliminm cuvntul cheie synchronized din cadrul metodei, iat ce obinem:
Child #2 starting.
Running total for Child
Child #1 starting.
Running total for Child
Running total for Child
Running total for Child
Running total for Child
Running total for Child
Running total for Child
Running total for Child
Running total for Child
Running total for Child
Sum for Child #1 is 29
Child #1 terminating.
Sum for Child #2 is 29
Child #2 terminating.

#2 is 1
#1
#2
#1
#2
#1
#2
#1
#1
#2

is
is
is
is
is
is
is
is
is

1
3
5
8
11
15
19
24
29

Evident, rezultatele pot varia n funcie de main, sistem de operare, etc.


Blocul synchronized
Dei crearea metodelor sincronizate, n cadrul claselor, este un mod eficient i uor de
sincronizare, nu funcioneaz pentru orice caz. De exemplu, s presupunem c dorim accesul sincronizat
la o metod care nu este declarat ca atare. Acest lucru se poate ntmpla n cazul n care clasa a fost
creat de altcineva i nu avem acces la codul surs. Pentru a accesa obiecte care nu au fost anterior
sincronizate, avem la dispoziie blocuri sincronizate:
synchronized(object)
{
//instruciunile ce trebuie sincronizate
}

Aici object este referina ctre obiectul de sincronizat. Un bloc sincronizat asigur faptul c
apelul unei metode membr a obiectului object, va avea loc ntr-un monitor al firului de execuie
apelant. De exemplu putem reedita programul mai sus considernd c metoda a fost declarat fr
cuvnt cheie synchronized:
int sumArray(int nums[])

Ceea ce se modific, este n cadrul metodei run() a clasei Thread:


public void run()
{
synchronized(sa)
{
int sum;
System.out.println(thread.getName() + " starting.");
answer = sa.sumArray(a);
System.out.println("Sum for " + thread.getName() +
" is " + answer);
System.out.println(thread.getName() + " terminating.");
}
}

n acest fel se asigur un calcul predictiv al sumei format din elementele lui sa.

Comunicarea ntre fire de execuie


S considerm urmtoarea situaie. Un fir numit T execut o metod sincronizat i are nevoie
de o resurs R, care este temporar indisponibil. Ce ar trebui s fac T? Dac T intr ntr-o bucl de
ateptare a lui R, blocheaz obiectul prevenind alte fire s o acceseze. Aceast abordare este
consumatoare de timp i poate duce la deadlock-uri adic blocare permanent. O alt soluie ar fi ca T
s elibereze temporar controlul obiectului, permind altui fir s ruleze. Cnd R devine disponibil, T
poate fi notificat i i poate relua execuia. Acest tip de abordare, presupune existena unei comunicri
ntre fire diferite. n Java aceasta se face prin metodele wait(), notify() i notifyAll().
Aceste metode aparin tuturor obiectelor, deoarece sunt implementate n clasa Object. Aceste
metode pot fi apelate dintr-o metod sincronizat. Atunci cnd un thread este blocat temporar,
apeleaz wait(). Aceasta va face ca firul s intre n sleep, i monitorul pentru acel obiect s fie eliberat,
permind altui fir s foloseasc obiectul. Mai trziu, firul ce era oprit, este trezit, atunci cnd un alt fir
care va intra n acelai monitor, apeleaz metoda notify(), sau notifyAll(). Un apel la metoda notify() va
reporni firul oprit.
Iat diversele forme ale metodei wait():
final void wait( ) throws InterruptedException
final void wait(long millis) throws InterruptedException
final void wait(long millis, int nanos) throws InterruptedException

Ultimele dou forme ale metodei semnific ateptarea pn cnd apare o notificare sau pn
cnd o perioada de timp, dat de parametrii, trece. Mai jos avem formele funciilor de notificare:
final void notify( )
final void notifyAll( )

Iat un exemplu de folosire a wait() i notify():


class TicTac
{
synchronized void tic(boolean running)
{
if(!running)
{ //opresc metoda
notify(); //anun cellalt thread
return;
}
System.out.print("Tic ");
notify(); //il las pe tac sa ruleze
try
{
wait(); // atept pe tac
}
catch(InterruptedException exc)
{
System.out.println("Thread interrupted.");
}
}
synchronized void tac(boolean running)
{
if(!running)
{ // opresc pe tac
notify(); // anun pe tic
return;
// adic cellalt fir
}
System.out.println("Tac");
notify(); // il las pe tic sa ruleze
try
{
wait(); // atept ca tic sa ruleze
}
catch(InterruptedException exc)
{
System.out.println("Thread interrupted.");
}
}
}
class MyThread implements Runnable
{
Thread thread;
TicTac ttOb;
// un nou fir de execuie
MyThread(String name, TicTac tt)
{

thread = new Thread(this, name);


ttOb = tt;
thread.start(); // pornesc firul
}
// ncep execuia firului
public void run()
{
if(thread.getName().compareTo("Tic") == 0)
{
// sunt in thread-ul tic
for(int i=0; i<5; i++) ttOb.tic(true);
//la sfarsit opresc tic
ttOb.tic(false);
}
else
{
// sunt in thread-ul tac
for(int i=0; i<5; i++) ttOb.tac(true);
//la sfarsit opresc tac
ttOb.tac(false);
}
}
}
class ThreadsDemo
{
public static void main(String args[])
{
TicTac tt = new TicTac();
MyThread mt1 = new MyThread("Tic", tt);
MyThread mt2 = new MyThread("Tac", tt);
try
{
mt1.thread.join();
mt2.thread.join();
}
catch(InterruptedException exc)
{
System.out.println("Main thread interrupted.");
}
}
}

n acest exemplu exist trei clase, TicTac, MyThread, ThreadsDemo. Prima clasa, conine
dou metode sincronizate:
synchronized void tic(boolean running)
synchronized void tac(boolean running)

Acestea, au urmtoarea logic:


if(!running)
{ //opresc metoda
notify(); //anun cellalt thread
return;
}
System.out.print("Tic ");
notify(); //il las pe tac sa ruleze
wait(); // atept pe tac

Dac parametrul transmis nu este nc false (mai am drept s rulez metoda) atunci afiez Tic i
dau dreptul lui Tac, urmnd s atept pn cnd thread-ul ce ruleaz Tac, s m ntiineze c a rulat n
monitor. Dac parametrul running este false, atunci anun cellalt fir de execuie i prsesc metoda.
Acelai lucru are loc i pentru metoda tac, evident referitor la thread-ul Tic.
Clasa MyThread, ce implementeaz interfaa Runnable, reprezint cele dou fire de execuie
suport pentru metodele tic() i tac(). n firul de execuie pentru Tic se apeleaz de cinci ori metoda tic()
cu parametru true, i ultima dat cu parametru false, pentru a permite deblocarea firului care executa
tac(). Acelai lucru este valabil viceversa pentru Tac.
n clasa ThreadsDemo se creeaz cele dou fire de execuie, se lanseaz i se ateapt
terminarea lor. Iat ce se ntmpl la rularea programului:
Tic
Tic
Tic
Tic
Tic

Tac
Tac
Tac
Tac
Tac

Dac modificm ns, metodele tac() i tic() eliminnd metodele de comunicare i anume

wait() i notify()rezultatul poate fi:


Tic Tic Tic Tic Tic Tac
Tac
Tac
Tac
Tac sau
Tac
Tac
Tac
Tac
Tac
Tic Tic Tic Tic Tic

Suspendarea, reluarea i oprirea firelor de execuie


Uneori este util s suspendm execuia unui thread. De exemplu, un fir de execuie poate fi
folosit pentru afiarea orei. Dac utilizatorul nu dorete vizualizarea orei, acest fir va fi suspendat. El va
putea fi repornit (resume) ulterior. Metodele ce realizeaz acest lucru sunt:
final void resume( )
final void suspend( )
final void stop( )

Totui folosirea acestora este ngrdit, de unele probleme cauzate de suspend() n trecut. De
exemplu, dac un thread obine un lock pe o zon critic, i el este suspendat, acele lock-uri sunt eterne.
n acest timp, alte fire de execuie ateapt deblocarea lor. Metoda resume() este de asemenea
nvechit. Nu cauzeaz probleme, dar nu poate fi folosit fr metoda suspend(). De asemenea metoda
stop() a fost considerat nvechit.
Urmtorul exemplu reprezint un mod n care se pot folosi aceste funcii:
class MyThread implements Runnable
{
Thread thread;
//volatile adic variabila poate fi
//modificata de alte parti ale programului
//cum ar fi un thread
volatile boolean suspended;
volatile boolean stopped;
MyThread(String name)
{
thread = new Thread(this, name);
suspended = false;
stopped = false;
thread.start();
}
// entry point pentru un fir de execuie
public void run()
{
System.out.println(thread.getName() + " starting.");
try
{
for(int i = 1; i < 1000; i++)
{
System.out.print(i + " ");
//din 10 in 10 are loc o pauza
if((i%10)==0)
{
System.out.println();
Thread.sleep(250);
}

//bloc sincronizat pentru verificarea


//stopped sau suspended
synchronized(this)
{
while(suspended)
{
wait();
}
//in cadrul opririi se iese din fir
if(stopped) break;
}
}
}
catch (InterruptedException exc)
{
System.out.println(thread.getName() + " interrupted.");
}
System.out.println(thread.getName() + " exiting.");
}
// stop firul de execuie
synchronized void mystop()
{
stopped = true;
// firul suspendat este oprit
suspended = false;
//anun firul care este suspendat
notify();
}
//suspendarea firului
synchronized void mysuspend()
{
suspended = true;
}
//Reluarea firului
synchronized void myresume()
{
suspended = false;
//anun firul care este suspendat
notify();
}
}
class SuspendDemo
{
public static void main(String args[])
{
MyThread ob1 = new MyThread("My Thread");
try
{

Thread.sleep(1000); //pauz pe firul principal


ob1.mysuspend(); //suspend firul ob1
System.out.println("Suspending thread.");
Thread.sleep(1000);
ob1.myresume(); //reiau firul ob1
System.out.println("Resuming thread.");
Thread.sleep(1000);
ob1.mysuspend();
System.out.println("Suspending thread.");
Thread.sleep(1000);
ob1.myresume();
System.out.println("Resuming thread.");
Thread.sleep(1000);
ob1.mysuspend();
System.out.println("Stopping thread.");
ob1.mystop();
}
catch (InterruptedException e)
{
System.out.println("Main thread Interrupted");
}
try
{
ob1.thread.join();
}
catch (InterruptedException e)
{
System.out.println("Main thread Interrupted");
}
System.out.println("Main thread exiting.");
}
}

Clasa MyThread, definete dou variabile booleane, suspended i stopped. Metoda run()
conine blocul sincronizat ce verific variabila suspended. Dac este true atunci metoda wait() este
apelat i suspend execuia firului. Metoda care stabilete valoarea variabilei la true, este
mysuspend(). Pentru a relua firul, avem myresume(), care seteaz variabila suspended pe true i
apeleaz notify(), pentru a relua execuia firului. Ultima metod este mystop() i conine paii care duc la
oprirea firului de execuie. n clasa SuspendDemo, metodele descrise sunt apelate n alternan pentru
a opri/relua execuia firului de cteva ori. Rezultatul rulrii acestui program este:
My Thread starting.
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 30
31 32 33 34 35 36 37 38 39 40
Suspending thread.

Resuming thread.
41 42 43 44 45 46 47 48
51 52 53 54 55 56 57 58
61 62 63 64 65 66 67 68
71 72 73 74 75 76 77 78
Suspending thread.
Resuming thread.
81 82 83 84 85 86 87 88
91 92 93 94 95 96 97 98
101 102 103 104 105 106
111 112 113 114 115 116
Stopping thread.
My Thread exiting.
Main thread exiting.

49
59
69
79

50
60
70
80

89 90
99 100
107 108 109 110
117 118 119 120

Grupuri de fire de execuie


Fiecare fir de execuie este membru al unui grup de fire. Un grup de thread-uri ofer un
mecanism pentru colectarea multiplelor fire ntr-un singur obiect pentru a manipula aceste fire. De
exemplu, se pot suspenda toate firele printr-o singur instruciune.
Dac vom crea un fir fr a specifica grupul din care face parte, automat va fi pus n acelai grup
cu cel al firului printe. Acest grup se mai numete i current thread group. Atunci cnd o aplicaie este
pornit, Java creeaz un TreadGroup numit main. Pentru a crea un grup separat de acesta, explicit, avem
la ndemn urmtoarele construcii:
public Thread(ThreadGroup group, Runnable target)
public Thread(ThreadGroup group, String name)
public Thread(ThreadGroup group, Runnable target, String name)

Mai jos exist un mod de folosire a acestor constructori:


ThreadGroup myThreadGroup = new ThreadGroup("My Group of Threads");
Thread myThread = new Thread(myThreadGroup, "a thread for my group");

Pentru a prelua grupul unui fir de execuie avem metoda getThreadGroup():


theGroup = myThread.getThreadGroup();

Iat un mod de folosire a acestei informaii:


public static void listCurrentThreads()
{
ThreadGroup currentGroup =
Thread.currentThread().getThreadGroup();
int numThreads = currentGroup.activeCount();
Thread[] listOfThreads = new Thread[numThreads];

currentGroup.enumerate(listOfThreads);
for (int i = 0; i < numThreads; i++)
{
System.out.println("Thread #" + i + " = "
+ listOfThreads[i].getName());
}
}

Aceast funcie preia numrul de fire din grup si le afieaz.


Alte metode
Clasa ThreadGroup conine cteva atribute i metode dintre care:

getMaxPriority i setMaxPriority pentru manipularea prioritii


getDaemon i setDaemon pentru controlul acestori tipuri de fire
getName pentru preluarea numelui
getParent i parentOf pentru manipularea numelui firului printe
Iat, spre exemplu, cum se poate stabili prioritatea maxim, att pe fir ct i pe grup:
class MaxPriorityDemo
{
public static void main(String[] args)
{
ThreadGroup groupNORM = new ThreadGroup(
"A group with normal priority");
Thread priorityMAX = new Thread(groupNORM,
"A thread with maximum priority");
// set Thread's priority to max (10)
priorityMAX.setPriority(Thread.MAX_PRIORITY);
// set ThreadGroup's max priority to normal (5)
groupNORM.setMaxPriority(Thread.NORM_PRIORITY);
System.out.println("Group's maximum priority = "
+ groupNORM.getMaxPriority());
System.out.println("Thread's priority = "
+ priorityMAX.getPriority());
}
}

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