Sunteți pe pagina 1din 8

1

Fire (de executare)


Considerm c lucrm pe un calculator cu np1 procesoare. Ne ndreptm
atenia asupra modului n care putem lansa n executare mai multe fire de executare
(procese).
Un fir (de executare) este un obiect de un tip special, pentru care este precizat
o aciune, ce este executat concurent cu aciunile altor fire.

1. Crearea i lansarea firelor de executare


Clasa Thread

Un prim mod de a crea i lansa n executare fire este oferit de clasa Thread
din pachetul java.lang:
public class Thread extends Object implements Runnable

clas ce conine metoda:


public void run() { }

Aceasta este metoda care va conine aciunile pe care trebuie s le ntreprind


firul. Pentru a preciza respectivele aciuni, va trebuie s extindem clasa Thread,
redefinind n noua clas metoda run:
class Tip extends Thread {

cmpuri
constructori

public void run() {

aciunile ce trebuie ntreprinse de ctre fir


}
}

alte metode
Crearea i lansarea n executare a unui fir se face astfel:

Tip Ob = new Tip(...);


Ob.start();

Funcioneaz urmtoarea convenie: prin invocarea metodei start este


invocat de fapt metoda run; ca urmare firul execut instruciunile din metoda run
concurent cu aciunile din firul care l-a lansat i cu eventualele alte fire active.
Executarea concurent a firelor de executare se face conform modelului aleator,
valabil pentru un calculator cu oricte procesoare:
Modelul aleator
Fiecare procesor execut repetat urmtoarele aciuni:
1) alege aleator un fir de executare "liber" (ale crui instruciuni nu sunt
n curs de executare);
2) execut un timp aleator instruciuni ale sale.

2
Observaii:
- modelul este independent de numrul real de procesoare;
- factorul aleator face ca executri diferite ale aceluiai program (ce folosete fire
de executare) pentru aceleai date de intrare pot conduce la rezultate diferite.
Cnd metoda run se termin, se termin i firul care a invocat-o. Un program
se termin atunci cnd se termin toate firele de executare lansate din cadrul su.
Exemplul 1. Programul urmtor lanseaz dou fire de executare i n
continuare tiprete 200 de '0'; cele dou fire tipresc cte 200 de '1', respectiv de
'2'. Ca urmare a utilizrii modelului aleator, la fiecare executare vor aprea 200 de
'0', 200 de '1' i 200 de '2', n diferite ordini.
class Tip extends Thread {
int i;
Tip(int i) { this.i = i; }

public void run() {


for(int k=0; k<200; k++) System.out.print(i + " ");
}

class Fire {
public static void main(String[] args) {
Tip Ob1 = new Tip(1), Ob2 = new Tip(2);
Ob1.start(); Ob2.start();
for(int k=0; k<200; k++) System.out.print("0 ");
}
}

Observaie. Dac nu s-ar apela la convenia de mai sus i Ob1 i Ob2 ar invoca
metoda run, atunci firul care invoc aceast metoda nu i-ar continua activitatea i ar
atepta ca cele dou obiecte s termine de executat metoda run, deci s-ar renuna la
esena firelor: executarea lor concurent! Concret, vor aprea 200 de '1', urmai de
200 de '2' i de 200 de '0'.
Observaie. Firul care lanseaz un nou fir nu are drepturi suplimentare: de
exemplu se poate termina naintea celui pe care l lanseaz.
Interfaa Runnable

O a doua modalitate de a crea i lansa n executare fire este de a folosi interfaa


Runnable din pachetul java.lang i care anun numai metoda run.
class Tip implements Runnable {
. . .
public void run() { . . . }
. . .
}
class C {
. . .
Tip Ob = new Tip(...);
Thread T = new Thread(Ob);

// firul este creat

3
T.start();
. . .

// firul este pornit

n aceast form apare clar faptul c obiectul Ob nu este ocupat dup pornirea
firului.

2. Un prim mod de sincronizare


Dac dintr-un fir lansm alt fir i dorim s ateptm terminarea acestuia nainte
de a trece la urmtoarea aciune, vom invoca metoda join a clasei Thread:
public final void join() throws InterruptedException

Dac modificm programul din Exemplul 1, insernd dup:


Ob1.start(); Ob2.start();

aciunea:
try {
Ob1.join(); Ob2.join();
}
catch(InterruptedException e) { }

cele 200 de caractere '0' vor fi tiprite ultimele.


Exemplul 2. Considerm programul urmtor:
import java.util.*;
class Tip extends Thread {
int k; boolean b = true;
public void run() {
while(b) System.out.print(" " + k++);
System.out.println("Fir");
}
}

public void terminare() { b = false; }

class P1 {
public static void main(String[] w) {
Scanner sc = new Scanner(System.in);
Tip Ob = new Tip(); Ob.start();
while ( sc.next().charAt(0) != 'q' );
Ob.terminare(); System.out.println("main");
}
}

Pe baza obiectului Ob este lansat un fir de executare. Acesta va tipri, atta


timp ct variabila boolean b are valoarea true, irul numerelor naturale. Firul de
executare principal prevede citirea repetat de iruri de caractere, pn cnd este
introdus un ir ce ncepe cu caracterul 'q'; n acel moment obiectul Ob execut
metoda terminare, care modific valorii lui b n false; ca urmare firul de
executare lansat este oprit. Firul de executare principal se termin i el.

4
Acest exemplu arat c obiectul pe baza cruia este lansat un fir de executare
(Ob n cazul nostru) nu este ocupat i deci poate invoca n continuare metode care sunt
executate concurent cu aciunile firului.
Dac n loc de Ob.start() am fi folosit Ob.run(), programul ar rula la
infinit.
Exemplul 3. Relum Exemplul 2, utiliznd interfaa Runnable n loc de clasa
Thread.
import java.util.*;
class Tip implements Runnable {
int k; boolean b = true;
public void run() {
while (b) System.out.print(" " + k++);
System.out.println("Fir");
}
public void terminare() { b = false; }
}
class P2 {
public static void main(String[] arg) {
Tip Ob = new Tip();
Thread T = new Thread(Ob);
T.start();
Scanner sc = new Scanner(System.in);
while (sc.next().charAt(0) != 'q');
Ob.terminare(); System.out.println("main");
}
}

Exemplul arat clar c obiectul Ob nu este ocupat dup pornirea firului.

4. Realizarea excluderii reciproce


n mod frecvent, aciunile ntreprinde de mai multe fire de executare active se
refer la unele resurse comune (variabile, obiecte, fiiere, baze de date etc.). n acest caz
pot aprea aspecte nedorite, specifice programrii concurente. Ne rezumm la problema
excluderii reciproce, ce reclam ca la fiecare moment de timp cel mult un fir de
executare s aib acces la resursele comune; dac un fir ncearc s acceseze resursele
comune cnd un alt fir le acceseaz, el va fi blocat.
Problema grdinilor ornamentale (atribuirii multiple). Intrarea n grdinile
ornamentale ale unui ora oriental se face prin mai multe pori. Se pune problema
inerii evidenei numrului n de persoane care au intrat n grdini.
O ncercare de rezolvare este urmtoarea:
- este introdus variabila global n, a crei valoare curent reprezint numrul
persoanelor ce au intrat n grdini;
- fiecrei pori i se asociaz n mod natural un proces (fir de executare n Java),
ce prevede ca n mod repetat valoarea variabilei n s fie mrit cu o unitate,
corespunztor intrrii unei persoane pe poarta respectiv.

5
ncercarea de mai sus nu este corect: n final n poate avea o valoare diferit de
cea ateptat; mai mult, la executri diferite ale programului ce transpune aceast
ncercare de rezolvare, se pot obine rezultate diferite. Explicaia const n primul rnd
n faptul c instruciunea n n+1 (asupra resursei/variabilei comune n) nu este
nedivizibil (nu este o aciune "atomic"), iar n al doilea rnd n modul de executare al
programelor concurente. ntr-adevr, instruciunea n n+1 este n realitate o
secven format din trei instruciuni n cod main:
LOAD n,r
INC r
STORE r,n

{ ncarc n n registrul r al CPU }


{ incrementeaz valoarea lui r cu 1 }
{ salveaz valoarea registrului n n }

Pentru exemplificare, s considerm cazul a dou pori (procese) P1 i P2. Fie


r1 i r2 registrele unitii centrale folosite de cele dou procese. Atunci n cazul n care
pe poarta P1 intr o persoan, iar pe poarta P2 intr dou persoane, este posibil (printre
altele) urmtoarea succesiune de aciuni (urmtorul scenariu):
Aciune
P1 ncarc
P1 incrementeaz
P2 ncarc
P2 incrementeaz
P2 salveaz
P2 ncarc
P2 incrementeaz
P2 salveaz
P1 salveaz

n
0

r1

r2

0
1
0
1
1
1
2
2
1

Rezultatul final este deci 1, dei n grdini au intrat 3 persoane!


ncepem prin a prezenta varianta care nu include sincronizare:
class C {
static int n; int r;
void delay() {
try { Thread.sleep( (int) (100 * Math.random()) ); }
catch(InterruptedException e) { }
}

void incr() {
r = n; delay(); r++; delay(); n = r; delay();
}

class Tip extends Thread {


C Ob = new C(); int i;
Tip(int i) { this.i=i; }
public void run() {
for (int j=1; j<=10; j++) {
Ob.incr(); System.out.print("
}
}
}

"+i);

6
class AtribMult0 {
public static void main(String[] s) {
Tip[] T = new Tip[5];
for (int i=0; i<5; i++) T[i] = new Tip(i);
for (int i=0; i<5; i++) T[i].start();
try { for (int i=4; i>=0; i--) T[i].join(); }
catch (InterruptedException e) { }
System.out.println("\nn = " + C.n);
}
}

S observm c dac instruciunea n n+1 (mai corect spus cele trei


instruciuni main n care se descompune) s-ar executa ca un tot (s-ar executa sub
excludere reciproc) , rezultatul ar fi cel corect.
n esen, excluderea reciproc presupune c dac un fir capt acces la
resursele comune, atunci:
- ntreprinde toate aciunile ce trebuie s le ntreprind asupra lor;
- n acest timp nici un alt fir nu poate cpta acces la resursele comune: dac
ncearc, este blocat i va fi deblocat la unul dintre momentele ulterioare cnd
nici un alt fir nu are acces la resursele comune.
Fie S secvena de instruciuni ce trebuie executat sub excludere reciproc,
adic secvena de instruciuni care face referire la resursele comune. Mecanismul
propus de Java pentru asigurarea excluderii reciproce este urmtorul:
- scriem o clas C cu o metod met, al crei corp este S;
- declarm metoda cu modificatorul synchronized;
- firele de executare care invoc metoda met trebuie s o
fac prin intermediul aceluiai obiect Ob de tipul C.
Soluia corect prevede excludere reciproc la incrementarea contorului:
class C {
static int n; int r;

void delay() {
try { Thread.sleep( (int) (100 * Math.random()) ); }
catch(InterruptedException e) { }
}
synchronized void incr() {
r = n; delay(); r++; delay(); n = r; delay();
}

class Tip extends Thread {


static C Ob; int i;
Tip(int i) { this.i=i; }

public void run() {


for (int j=1; j<=10; j++) {
Ob.incr(); System.out.print("
}
}

"+i);

7
class AtribMult {
public static void main(String[] s) {
Tip.Ob = new C();
Tip[] T = new Tip[5];
for (int i=0; i<5; i++) T[i] = new Tip(i);
for (int i=0; i<5; i++) T[i].start();
try { for (int i=4; i>=0; i--) T[i].join(); }
catch (InterruptedException e) { }
System.out.println("\nn = " + C.n);
}
}

Este uor de verificat c dac nu declarm metoda incr cu modificatorul


synchronized sau dac firele nu invoc metoda incr prin intermediul aceluiai
obiect de tipul C, rezultatul final nu va fi neaprat cel corect.
Exemplul 4. Dorim s calculm suma elementelor unui tablou a de lungime n.
Pentru aceasta apelm la un numr de nr_fire fire de executare. Fiecare fir ia n
mod repetat din tablou primul element nc neatins i l adaug la suma sa parial. n
final este calculat suma dorit ca sum a sumelor pariale.
O prim particularitate a programului Sumator care urmeaz este c n main
singura aciune const n crearea un obiect anonim de tipul Sumator. Deci n
continuare totul se reduce la constructorul acestei clase. Obiectul anonim este
transmis i firelor, care l vor folosi pentru accesarea cmpurilor i metodelor clasei
Sumator, nemaifiind necesar ca ele s fie statice.
-

Resursele (comune) din clasa Sumator sunt urmtoarele:


tabloul a de lungime n;
suma suma a elementelor tabloului;
indicele curent indice al primului element din a care nu a fost nc atins;
nt = numrul sumelor pariale (termenilor) adugai de fire sumei suma.

Metodele care folosesc resursele comune sunt:


indiceNou: ntoarce primul indice din a nc nefolosit;
adunap: incrementeaz suma cu o sum parial (calculat de un fir).

import java.util.*;
public class Sumator {
public int[] a;
private int suma=0, indice=0;
private int nt=0; // cati termeni au fost adunati la suma
private int n=0, nr_fire=10;
public Sumator() {
Scanner sc = new Scanner(System.in);
System.out.print("n = "); n = sc.nextInt();
a = new int[n];
for (int i=0; i<n; i++) a[i] = i;
for (int i=0; i<10; i++)
new Fir(this,i).start();
}
public synchronized int indiceNou() {

8
if (indice < n) return indice++;
else return -1;
}
public synchronized void adunap(int sumap) {
suma += sumap;
if (++nt == nr_fire)
System.out.println("Suma totala este " + suma);
}
public static void main(String args[]) {
new Sumator();
}
}
class Fir extends Thread {
// nr = nr. de ordine al firului;
// sumap = suma partiala calculata de firul curent
int sumap = 0, nr;
Sumator s;
public Fir(Sumator s, int nr){
this.s = s; this.nr = nr;
}
public void run() {
int indice = s.indiceNou();
while(indice != -1) {
try { Thread.sleep(2); }
catch(InterruptedException ie) { }
sumap += s.a[indice];
indice = s.indiceNou();
}
System.out.println("Suma partiala a firului "
+ nr + " este " + sumap);
s.adunap(sumap);
}
}

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