Prin multitasking se intelege executia cvasi-simultana a mai multor programe (sarcini) de catre acelasi calculator. Fiecare proces concurent executat in regim de multitasking este un fir de executie.
Rularea concurenta a programelor pe un computer monoprocesor se face prin multiplexarea lor in timp. Astfel, daca la un moment dat sunt operative trei programe: A, B, si C de complexitate relativ egala, microprocesorul va lucra aproximativ cate o treime din fiecare secunda la executia fiecaruia dintre ele.
Idea de multitasking nu este o noutate. In mod curent pe computerele Macintosh sau pe PC-uri sub Windows se lucreaza cu mai multe ferestre deodata, in fiecare dintre aceste ferestre executandu-se sarcini diferite.
In Java, firele de executie sunt concepute ca parte integranta a limbajului 351;i sunt implementate ca obiecte ale clasei Thread. Aceasta clasa are un set de metode constructor intre care cele mai utilizate sunt:
public Thread(),
public Thread(Runable Target).
unde Runnable este o interfata ce cuprinde metoda run().
In cele ce urmeaza se vor prezenta o serie de alte metode ale clasei Thread care, asa cum se va vedea, reprezinta comenzile ce guverneaza intreaga existenta a firelor de executie.
Un fir de executie Java este la fel ca orice program Java independent de platforma. Detaliile legate de realizarea multitasking-ului pe calculatoare diferite (Macintosh, PC Unix, etc.) sunt rezolvate direct de limbaj, deci nu necesita o atentie speciala din partea programatorului.
Desfasurarea concurenta a doua fire de executie concurente in timpul rularii unui program se face conform diagramei din Figura 11.1.
Fir A |
Pornire |
Asteapta |
Executie |
Asteapta |
Executie |
Oprire |
|
Fir B |
Asteapta |
Pornire |
Asteapta |
Executie |
Asteapta |
Executie |
Oprire |
Figura 11.1 Rularea concurenta a doua fire de executie intr-un program Java
Prin urmare perioada de existenta a unui fir de executie se compune din mai multe etape care se desfasoara in inter-relatie cu alte fire si bineinteles cu microprocesorul executant. Aceste stadii de viata sunt:
Nasterea;
Asteptarea;
Rularea;
Blocarea;
Oprirea.
Nasterea este etapa cand firul de executie este creat, are alocata memoria RAM necesara si datele private au fost initializate. In aceasta stare firul nu ruleaza, dar poate fi programat pentru rulare prin metoda
public syncronized void start()
sau poate fi oprit cu metoda:
public final void stop().
Momentul urmator corespunde starii de asteptare “gata de executie“, cand firul a fost deja programat si se plaseaza intr-o coada de asteptare pentru a intra in functiune. Pozitia ocupata in coada este functie de momentul nasterii si de nivelul de prioritate pe care il are firul respectiv.
Stadiul de rulare corespunde momentului in care firul de executie se lanseaza urmare a comenzii start() ce apeleaza metoda run() lansand executia propriu-zisa: Firul devine stapan pe resursele sistemului de calcul si, prin urmare, microprocesorul executa comenzile aferente lui. Parasirea acestei stari se face fie prin cedarea controlului cu metoda
public static void yeld(),
fie prin trecerea in stare de “somn” pentru o anumita durata cu metodele:
public static void sleep(durata_in_ms);
public final void suspend();
public final void wait
Un fir de executie blocat cu yeld() este in afara cozii, dar asteapta un prilej pentru a reintra in situatia de a relua executia. Durata blocarii este dependenta de cauza care a produs-o. Un fir de executie “adormit” cu sleep() este blocat o perioada de timp fixata la adormire. Un fir de executie oprit cu suspend() asteapta comanda resume() pentru a continua, iar unul blocat cu wait() asteapta o schimbare de conditie intr-un alt fir de executie concurent.
Oprirea firelor de executie se face fie la terminarea sarcinilor aferente lui, fie la o anumita actiune a unui alt fir concurent, cu metoda stop(). Un fir de executie oprit este mort si deci, se elibereaza memoria alocata lui.
Testarea faptului daca un fir de executie este sau nu activ se face cu metoda:
public final native boolean is_Alive()
Asa cum s-a precizat toate functiile de mai sus apartin clasei Thread. Ea cuprinde de asemenea variabilele:
public final static int MIN_PRIORITY=0 (prioritate minima);
public final static int NORM_PRIORITY=5(prioritate normala);
public final static int MAX_PRIORITY=10 (prioritate maxima).
care reglementeaza rangurile de prioritate ale firelor de executie dintr-o aplicatie si metoda
public final void setPriority(int prioritate)
cu care se face setarea prioritatii.
Exemplul urmator ilustreaza utilizarea metodelor si variabilelor anteriore:
if(un_fir.is_Alive())
else
In blocul de program de mai sus se executa o testare a faptului ca “un_fir” este activ. Daca da i se seteaza prioritatea normala daca nu este pornit si i se seteaza prioritatea maxima.
T.11.1.1. Scrieti un bloc de program care sa testeze daca firul “A” este activ si daca da, sa stabileasca o prioritate minima pentru “A”; iar daca nu, sa porneasca firul “A”;
Crearea unui fir de executie se face prin derivarea unei clase proprii din clasa Thread. Pentru a intelege mai exact functionarea unui program multitasking se da mai jos un exemplu simplu avand doua fire.
class Contor extends Thread
public void run() catch (InterruptedException e)
class Crestere catch (InterruptedException e)
//Instructiuni bucla de asteptare
Atata vreme cat firul “un_contor” este “in viata” (un_contor.is_Alive() returneaza true) programul ramane in bucla while. La fiecare trecere firul “Crestere” afiseaza mesajul “Numarare…” si datorita comenzii sleep() timp de 10 ms firul un_contor avand acelasi nivel prioritate preia controlul sistemului si se incrementeaza. Daca apare exceptia de tip InterruptException se iese din bucla. Daca lucrurile se desfasoara normal (fara intreruperi) rezultatul va fi afisarea pe ecran a urmatoarelor linii
Numarare…
Numararea a ajuns la 1;
Numarare…
Numararea a ajuns la 2;
Numarare…
Numararea a ajuns la 3;
Numarare…
Numararea a ajuns la 24;
Alte metode importante pentru functionarea in regim de multitasking sunt:
public final int getPriority() returneaza nivelul de prioritate al firului;
public static native Thread currentThread() returneaza firul activ curent;
public void exit() face “curatenie” inainte de iesirea din functiune.
T.11.1.1. Raspundeti la urmatoarele intrebari:
1. Conceptul de multitasking inseamna:
a) Executia simultana a mai multor sarcini;
b) Multiplexarea in timp a sarcinilor de executat;
c) Executia sarcinilor pe mai multe procesoare paralele.
2. Un fir de executie se comporta in timpul rularii ca:
a) Un bloc al programului;
b) Un pachet de clase;
c) aplicatie autonoma;
3. Un fir intra in executie atunci cand:
a) Se naste;
b) Are prioritate
c) Primeste comanda start()
4. Un fir de executie este blocat daca primeste comanda:
a) sleep();
b) wait();
c) stop().
5. Un fir de executie este oprit daca:
a) Este intrerupt de alt fir concurent;
b) S-au executat toate actiunile aferente lui;
c) Intreaga executie a programului este oprita.
Deoarece firele de executie ruleaza pe acelasi microprocesor si isi impart intre ele aceiasi memorie RAM, exista posibilitatea ca ele sa foloseasca in comun o serie de variabile metode sau obiecte.
Rezulta de aici ca, daca, spre exemplu, doua fire de executie folosesc un obiect in sensul ca unul creeaza obiectul iar celalalt il manipuleaza, este foarte important ca ordinea de executie a firelor sa fie controlata.
O desfasurare nefavorabila a lucrurilor ar putea, in cazul exemplului citat, sa duca la incercarea de folosire a unui obiect neinstantiat. Evitarea unor asemenea situatii se face utilizand metodele sincronizate.
Sincronizarea este de asemenea utila pentru evitarea situatiilor in care mai multe fire de executie acceseaza simultan o aceiasi resursa (de exemplu un fisier in care se fac operatii I/O).
Sincronizarea unei anumite metode se face prin plasarea in fata numelui ei a specificatorului synchronized. Prin urmare sintaxa de declarare a unei asemenea metode devine:
[specif_acces] synchronized tipul_retur nume_met (parametrii)
syncronized public void print(nume_variabila)
T.11.2.1. Declarati o metoda public void print_it() care sa fie protejata impotriva erorilor de nesincronizare in aplicatiile cu fire multiple.
Dintr-o clasa care are una sau mai multe metode sincronizate se instantiaza obiecte care au o caracteristica speciala; nu accepta actiunea simultana a doua metode sincronizate asupra lor. In acest fel se evita situatia conflictuala in care doua fire de executie se “ciocnesc” la utilizarea unei aceleiasi resurse (obiect).
Pentru ca un fir de executie sa poata rula el trebuie sa aiba o metoda run() in interiorul lui sau al tintei sale. Metoda run() este definita simplu:
public void run()
si este modificata prin polimorfism astfel incat sa satisfaca cerintele concrete ale firului de executie pe care il deserveste.
Este foarte important de avut in vedere includerea unor manipulatoare de exceptii in scrierea variantelor polimorfe ale lui run(), deoarece situatii de exceptie apar frecvent in cazul rularii concurente a mai multor fire de executie.
O modalitate simpla de rezolvare a iesirii din functiune a firului de executie in cazul aparitiei oricarui tip de exceptie este modificarea in exemplul de la paragraful anterior a blocului:
while(un_contor.is_Alive()) catch (Exception e)
Daca apare o exceptie de oarecare se iese din bucla daca nu se poate continua rularea pana la atingerea valorii Max a contorului.
T.11.2.2. Modificati blocul de program scris anterior pentru a comanda stoparea unui fir de executie doar la aparitia exceptiilor InterruptException si ArithmeticException.
In cazul in care insa se intentioneaza o tratare mai atenta a exceptiilor se recomanda utilizarea unor structuri mai sofisticate de manipulatori care sa asigure o mai mare selectivitate in detectia si procesarea situatiilor speciale.
T.11.2.3. Raspundeti la urmatoarele intrebari
1. Evitarea aparitiei in aplicatiile multitasking a unor erori datorate folosirii in comun de mai multe fire a resurselor sistemului se face cu:
a) Blocuri de testare exceptii try…catch;
b) Metode sincronizate;
c) Utilizarea unei singur obiect Thread intr-un program.
2. Declararea unei metode sincronizate se face cu sintaxa:
a) [sp_acces] synchronized nume_met(parametrii)
b) [sp_acces] synchronized tip_ret nume_met
c) synchronized tip_ret nume_met
3. Metodele sincronizate nu permit:
a) Aparitia exceptiilor;
b) Sa fie accesat simultan de mai multe fire
c) Aparitia unor fire suplimentare dup instantierea sa;
4. Metoda run() este :
a) Componenta a interfetei Runnable;
b) Modificata (polimorfism) in clasele fir de executie;
c) Apelata la comanda start().
5. Tratarea exceptiilor in cazul firelor se poate face prin:
a) Includerea unor blocuri de testare;
b) Metode sincronizate;
c) Stabilirea prioritatilor.
Prin multitasking se intelege executia cvasi-simultana a mai multor programe de catre acelasi calculator. Fiecare proces concurent executat in regim de multitasking este un fir de executie.
In Java, firele de executie sunt concepute ca parte integranta a limbajului 351;i sunt implementate ca obiecte ale clasei Thread. Aceasta clasa are un set de metode care reprezinta comenzile ce guverneaza intreaga existenta a firelor de executie.
Deoarece firele de executie ruleaza pe acelasi microprocesor si isi impart intre ele aceiasi memorie RAM, exista posibilitatea ca ele sa foloseasca in comun o serie de variabile metode sau obiecte. Rezulta de aici ca, este foarte important ca ordinea de executie a firelor sa fie controlata. Controlul se exercita prin sincronizare.
Este foarte important de avut in vedere includerea unor manipulatoare de exceptii in scrierea variantelor polimorfe ale lui run(), deoarece situatiile de exceptie apar frecvent in cazul rularii concurente a mai multor fire de executie.
Raspunsuri
T.11.1.1.
if(A.is_Alive())else A.start();
T.11.1.2.1. b
T.11.1.2.2. c
T.11.1.2.3. b, c
T.11.1.2.4. a, b
T.11.1.2.5. a, b, c
T.11.2.1.
syncronized public void print_it ()
T.11.2.2.
while(un_contor.is_Alive()) catch (InterruptException e1)
} catch (ArithmeticException e2) }}
T.11.2.3.1. a
T.11.2.3.2.
T.11.2.3.3. b
T.11.2.3.4. a, b, c
T.11.2.3.5. a