Sunteți pe pagina 1din 16

smaranda.belciug@inf.ucv.ro Laborator 9 Threads Computerele sunt multitask, adic pot face mai multe lucruri n acelai timp.

Un computer care are un singur CPU nu poate efectiv s fac dou lucruri n acelai timp, dar poate s-i ndrepte atenia asupra unuia sau mai multor task-uri. Pentru a folosi la putere maxim aceast capacitate a computerelor, un programator trebuie s fac programare n paralel, adic s scrie un program care s execute mai multe task-uri simultan. n Java un task se numete thread. Termenul thread se refer la fir de control sau fir de execuie, o secven de instruciuni care sunt executate una dup alta. Intr-un program multithreaded pot fi mai multe fire de control, mergnd n paralel. Fiecare program Java are cel puin un thread execuia rutinei main. ntr-un GUI exist cel puin nc un thread responsabil cu tratarea evenimentelor i desenarea componentelor pe ecran. Thread-ul pentru GUI este creat n momentul deschiderii primei ferestre. Cnd rutina main deschide o fereastr, thread-ul main i thread-ul GUI merg n paralel. Crearea i pornirea thread-urilor In Java, un thread este un obiect de tip java.lang.Thread (sau a unei subclase a acestei clase). Scopul unui obiect de tip Thread este acela de a executa o singur metod. Metoda este executat n firul ei de execuie, care poate rula n paralel cu alte thread-uri. n momentul opririi execuiei unei metode, fie datorit terminrii normale a metodei, fie din cauza unei excepii, thread-ul se oprete din execuie. n momentul n care acest lucru se ntmpl, nu se mai poate porni din nou thread-ul i nici nu se mai poate folosi acelai obiect Thread pentru a porni alt thread. Exist dou metode de a programa un thread. Prima este aceea de a crea o subclas a clasei Thread i de a defini metoda public void run() din subclasa. n metoda run() se definete task-ul pe care trebuie s-l ruleze thread-ul. n momentul nceperii unui thread, metoda run() va fi executat n acel thread. Mai jos este un exemplu: o clas care definete un thread care nu face nimic altceva dect s afieze un mesaj.
public class NamedThread extends Thread { private String name; // Numele thread-ului public NamedThread(String name) { // Constructorul da numele thread-ului this.name = name; } public void run() { // Metoda run afiseaza un mesaj System.out.println("Salutari de la thread-ul " + name + "'!"); } }

smaranda.belciug@inf.ucv.ro Pentru a folosi un NameThread, trebuie evident creat un obiect cre aparine acestei clase.
NamedThread greetings = new NamedThread("Fred");

Totui, crearea unui obiect nu nseamn c thread-ul o s porneasc automat. Trebuie apelat metoda start().
greetings.start();

Scopul metodei start() este acela de a crea un nou thread de control care va executa metoda run() a obiectului Thread. Noul thread va rula n paralel cu thread-ul care apelat metoda start() i cu alte thread-uri deja existente. Practic, codul din metoda run() va fi executat n acelai timp cu celelalte instruciuni care urmeaz dup apelarea metodei greetings.start().
NamedThread greetings = new NamedThread("Fred"); greetings.start(); System.out.println("Thread-ul a pornit.");

Dup ce metoda greetings.start() este executat, exist dou thread-uri. Unul dintre ele va afia Thread-ul a pornit n timp ce cellalt vrea s afieze Salutari de la thread-ul Fred!. Este important de menionat c aceste mesaje pot fi afiate n orice ordine. Cele dou thread-uri ruleaz simultan i vor concura pentru acces la consol. Apelarea funciei greetings.start() este foarte diferit de greetings.run(). Apelarea metodei greetings.run() va executa metoda run() n acelai thread, nu o s creeze un alt thread. Toat munca din run() va fi terminat n momentul n care computerul trece la urmtoarea instruciune ce urmeaza dup greetins.run(). Exist dou moduri de a programa un thread. Primul mod este acela de a defini o subclas a clasei Thread. Cel de-al doilea mod este acela de a defini o clas care implementeaz interfaa java.lang.Runnable. Interfaa Runnable definete o singur metod public void run(). Un obiect care implementeaz interfaa Runnable poate fi considerat ca parametru n contructorului unui obiect de tip Thread. Cnd este apelat metoda start() a thread-ului, acesta va executa metoda run() din obiectul Runnable.
public class NamedRunnable implements Runnable { private String name; // Numele thread-ului public NamedRunnable(String name) { // Constructorul care da numele obiectului this.name = name; } public void run() { // Metoda run afiseaza un mesaj System.out.println("Salutari de la thread-ul '" + name +"'!"); } }

smaranda.belciug@inf.ucv.ro Crem un obiect NamedRunnable, dup care l folosim pentru a crea un obiect de tip Thread.
NamedRunnable greetings = new NamedRunnable("Fred"); Thread greetingsThread = new Thread(greetings); greetingsThread.start();

-----Thread greetingsFromFred = new Thread() { public void run() { System.out.println("Salutari de la Fred!"); } }; greetingsFromFred.start();

Exemplu. S considerm urmtorul program care creeaz mai multe thread-uri. Fiecare thread face acelai task, acela de a numra ntregii mai mici de 1000000 care sunt numere prime.
/** * Un program care porneste mai multe thread-uri, fiecare dintre ele * facand acelasi calcul. Utilizatorul specifica numarul de threaduri. * Scopul programului este acela de a observa ca thread-urile termina * calculul intr-o ordine nedeterminata anterior */ import java.util.Scanner; public class ThreadTest1 {

/** * Cand ruleaza un thread care apartine aceste clase, se numara * toate numerele prime dintre 2 si 1000000. Se va afisa rezultatul, * plus id-ul thread-ului, plus cate secunde a durat calculul. */ private static class CountPrimesThread extends Thread { int id; // un numar id pentru acest thread specificat in constructor public CountPrimesThread(int id) { this.id = id; } public void run() { long startTime = System.currentTimeMillis(); int count = countPrimes(2,1000000); long elapsedTime = System.currentTimeMillis() - startTime; System.out.println("Thread-ul " + id + " a numarat " + count + " numere prime in " + (elapsedTime/1000.0) + " secunde."); } }

/** * Pornim mai multe thread-uri. Numarul de thread-uri trebuie sa fie * intre 1 si 25 si este specificat de utilizator */

smaranda.belciug@inf.ucv.ro
public static void main(String[] args) { int numberOfThreads = 0; while (numberOfThreads < 1 || numberOfThreads > 25) { Scanner in = new Scanner(System.in); System.out.print("Cate thread-uri vrei sa folosesti (intre 1 si 25) ? "); numberOfThreads = in.nextInt(); in.close(); if (numberOfThreads < 1 || numberOfThreads > 25) System.out.println("Nu te mai juca! Am spus un numar intre 1 si 25!"); } System.out.println("\nCreez " + numberOfThreads + " thread-uri care numara numere prime..."); CountPrimesThread[] worker = new CountPrimesThread[numberOfThreads]; for (int i = 0; i < numberOfThreads; i++) worker[i] = new CountPrimesThread( i ); for (int i = 0; i < numberOfThreads; i++) worker[i].start(); System.out.println("Thread-uri au fost create si au pornit."); }

/** * Calculeaza si returneaza numarul de numere prime dintr-un interval * inchis dat */ private static int countPrimes(int min, int max) { int count = 0; for (int i = min; i <= max; i++) if (isPrime(i)) count++; return count; }

/** * Metoda care calculeaza daca un numar x este prim sau nu * Presupunem ca x este mai mare ca 1 */ private static boolean isPrime(int x) { assert x > 1; int top = (int)Math.sqrt(x); for (int i = 2; i <= top; i++) if ( x % i == 0 ) return false; return true; }

} // end class ThreadTest1

Cate thread-uri vrei sa folosesti (intre 1 si 25) ? Creez 25 thread-uri care numara numere prime... Thread-uri au fost create si au pornit.

25

smaranda.belciug@inf.ucv.ro
Thread-ul Thread-ul Thread-ul Thread-ul Thread-ul Thread-ul Thread-ul Thread-ul Thread-ul Thread-ul Thread-ul Thread-ul Thread-ul Thread-ul Thread-ul Thread-ul Thread-ul Thread-ul Thread-ul Thread-ul Thread-ul Thread-ul Thread-ul Thread-ul Thread-ul 0 a numarat 78498 numere prime in 25.966 secunde. 1 a numarat 78498 numere prime in 25.962 secunde. 3 a numarat 78498 numere prime in 25.963 secunde. 9 a numarat 78498 numere prime in 25.971 secunde. 10 a numarat 78498 numere prime in 25.971 secunde. 18 a numarat 78498 numere prime in 25.984 secunde. 20 a numarat 78498 numere prime in 25.999 secunde. 21 a numarat 78498 numere prime in 25.996 secunde. 22 a numarat 78498 numere prime in 25.996 secunde. 23 a numarat 78498 numere prime in 25.996 secunde. 19 a numarat 78498 numere prime in 26.16 secunde. 11 a numarat 78498 numere prime in 26.421 secunde. 12 a numarat 78498 numere prime in 26.39 secunde. 13 a numarat 78498 numere prime in 25.974 secunde. 14 a numarat 78498 numere prime in 26.328 secunde. 24 a numarat 78498 numere prime in 26.01 secunde. 2 a numarat 78498 numere prime in 26.722 secunde. 8 a numarat 78498 numere prime in 26.528 secunde. 7 a numarat 78498 numere prime in 26.558 secunde. 6 a numarat 78498 numere prime in 26.59 secunde. 5 a numarat 78498 numere prime in 26.621 secunde. 4 a numarat 78498 numere prime in 25.971 secunde. 17 a numarat 78498 numere prime in 26.234 secunde. 16 a numarat 78498 numere prime in 26.266 secunde. 15 a numarat 78498 numere prime in 26.297 secunde. 3

Cate thread-uri vrei sa folosesti (intre 1 si 25) ?

Creez 3 thread-uri care numara numere prime... Thread-uri au fost create si au pornit. Thread-ul 0 a numarat 78498 numere prime in 2.951 secunde. Thread-ul 1 a numarat 78498 numere prime in 2.945 secunde. Thread-ul 2 a numarat 78498 numere prime in 2.943 secunde.

Operaii pe Thread-uri S presupunem c thrd este un obiect de tip Thread. thrd.isAlive() poate fi folosit pentru a testa dac un thread este n via(alive). Se spune c un thread este n via de la nceperea pn la finalizarea task-ului su. Dup ce thread-ul termin, el devine mort (dead). Thread.sleep(milliseconds) metoda de clas face ca thread-ul s doarm pentru numrul specificat de milisecunde. Un thread care doarme este n via, dar nu ruleaz nimic. Metoda sleep() poate arunca o excepie de tip InterruptedException. Un thread poate s ntrerup un alt thread atunci cnd acesta doarme . Se folosete metoda thrd.interrupt(). Nu este nevoie ca un thread s atepte s moar alt thread. Dac un alt thread apeleaza thrd.join(), acel thread doarme pn cnd thrd termin. Dac thrd este deja mort n momentul apelrii thrd.join(), thread-ul care a apelat metoda pornete imediat. Metoda join() poate arunca o InterruptedException care trebuie tratat.
CountPrimesThread[] worker = new CountPrimesThread[numberOfThreads];

smaranda.belciug@inf.ucv.ro
long startTime = System.currentTimeMillis(); for (int i = 0; i < numberOfThreads; i++) { worker[i] = new CountPrimesThread(); worker[i].start(); } for (int i = 0; i < numberOfThreads; i++) { try { worker[i].join(); // Doarme pana cand work[i] isi termina executia } catch (InterruptedException e) { } } // in acest moment toate thread-urile si-au terminat executia long elapsedTime = System.currentTimeMillis() - startTime; System.out.println("timp: " + (elapsedTime/1000.0) + " secunde.");

Se poate observa c n codul de mai sus se presupune ca nu o s intervin nicio InterruptedException. Ar trebui nlocuit codul astfel:
while (worker[i].isAlive()) { try { worker[i].join(); } catch (InterruptedException e) { } }

Sychronized Programarea ctorva thread-uri care s lucreze independent e uoar. Dificultatea apare cnd dorim ca thread-urile s interacioneze. Cnd dou thread-uri au nevoie de acces la aceeai resurs (variabil, fereastr) trebuie s avem grij ca acea resurs s nu fie cerut de alt thread. S analizm urmtoarea instruciune: count = count + 1; Exist trei operaii aici: 1. Afl valoarea lui count 2. Adaug 1 la acea valoare 3. Stocheaza noua valoare n count S presupunem c mai multe thread-uri realizeaz aceti pai. Dou thread-uri pot s ruleze n acelai timp, chiar dac avem doar un procesor. S prespunem c un thread este ntre pasul 2 i pasul 3, altul ncepe s execute pasul 1. Deoarece primul thread nu a stocat nc valoarea nou n count, cel de-al doilea citete deja fosta valoare i adug 1 n ea. Dup ce ambele thread-uri execut pasul 3, valoarea lui count a crescut doar cu 1, nu cu 2! Avem de a face cu race condition. S mai lum un exemplu:

smaranda.belciug@inf.ucv.ro if (A != 0) B = C / A; Dac la variabila A au acces mai multe thread-uri, atunci se poate ca un al doilea thread s modifice valoarea lui A n 0, n timp ce un prim thread a trecut de condiia de if i se pregtete s fac divizunea. S-a ajunge la o mprire prin 0!!! Pentru a rezolva problema aceasta, trebuie ca un thread s aib acces exclusiv la o anumit resurs. Acest lucru se realizeaz cu metode de sincronizare (sychronized methods) i declaraii de sincronizare (sychronized statements). Sincronizarea n Java folosete o excludere mutual. Accesul exclusiv este garantat, doar daca toate thread-urile folosesc sincronizarea. Exemplu.
public class ThreadSafeCounter { private int count = 0; // Valoarea counter-ului

synchronized public void increment() { count = count + 1; } synchronized public int getValue() { return count; } }

Dac tsc este o instan a clasei ThreadSafeCounter, atunci orice thread poate apela metoda tsc.increment() pentru a aduga 1 la counter n deplin siguran. Pentru c tsc.increment() este sychronized nseamn c doar un thread poate s foloseasc metoda la un moment dat; odat ce un thread ncepe s execute metoda, este garantat faptul c pn o va termina de executat, niciun alt thread nu o sa modifice valoarea lui tsc.count. Garania depinde de faptul c variabila count este private. Acest lucru foreaz ca accesul la aceast variabil s fie fcut prin metoda sychronized. Dac variabila era public, un thread putea s ocoleasc sincronizarea i s execute instruciunea tsc.count++. Sincronizarea nu garanteaz acces exclusiv, garanteaz doar excludere mutual (mutual exclusion) ntre thread-urile sincronizate.
if ( tsc.getValue() == 0 ) doSomething();

Instruciunile de mai sus pot ntmpina probleme. Pentru a rezolva situaia codul ar trebui s arate astfel:
synchronized(tsc) { if ( tsc.getValue() == 0 ) doSomething();

smaranda.belciug@inf.ucv.ro
}

Sintaxa lui synchronized este:


synchronized( object ) { statements }

n Java, excluderea mutual este ntotdeauna asociat cu un obiect; spunem c sincronizarea se face pe un obiect anume. Regula de aur a sincronizrii n Java este: Doua thread-uri nu pot fi sincronizate n acelai timp, pe acelai obiect. Dac un thread este sincronizat pe un obiect, i un al doilea thread ncearc s se sincronizeze cu acelai obiect, el va fi forat s atepte pn cnd primul a terminat lucrul cu obiectul respectiv. Acest lucru se implementeaz folosind cuvntul lock. Fiecare obiect are un lock, care poate s fie la un singur thread ntr-un anumit moment n timp. Pentru a declara o sincronizare, un thread trebuie s obin mai ntt lock-ul obiectului respectiv. Dac el este valabil, atunci thread-ul obine imediat lock-ul i ncepe s-i execute codul de sincronizare. El va elibera lock-ul n momentul finalizrii execuiei codului. Un thread care nu poate s obin pe loc lock-ul, va intra n modul sleep i va iei n momentul n care lock devine valabil. Exemplu. S presupunem ca dorim s calculm numerele prime, dar s mprim problema pentru a fi rezolvat de mai multe thread-uri. Fiecare thread va avea un interval n care s calculeze numerele prime. La final fiecare va trebui s adune numrul obinut la totalul obinut. Variabila total este valabil pentru toate threadurile. Dac fiecare thread o s calculeze:
total = total + count;

exist o ans ca dou thread-uri s ncerce operaia de mai sus n acelai timp, iar rezultatul total s fie greit. Pentru a preveni aceast situaie acces-ul la total trebuie s fie sincronizat.
synchronized private static void addToTotal(int x) { total = total + x; System.out.println(total + " numere prime pana la acest moment."); }

/** * Acest program calculeaza numarul de numere prime dintre * 3000001 si 6000000. Munca poate fi divizata intre maxim 5 threaduri. * Numarul de thread-uri folosite va fi ales de utilizator. */ import java.util.Scanner;

smaranda.belciug@inf.ucv.ro

public class ThreadTest2 { /** * Alegem o variabila start de la care sa pornim interval in care cautam * numere prime. Intervalul este [(start+1), (2*start)]. Interval a fost ales * astfel incat sa fie divizibil cu 2,3,4 si 5 pentru a fi usor impartit * intre 5 thread-uri. */ private static final int start = 3000000; /** * numarul total de numere prime gasite. Fiecare thread calculeaza * numerele prime din intervalul sau. La final aduna numarul gasit la * total. */ private static int total; /** * Adauga x la total. Aceasta metoda este sincronizata pentru a fi * folosita in siguranta de mai multe thread-uri. */ synchronized private static void addToTotal(int x) { total = total + x; System.out.println(total + " numere prime gasite pana acum."); } /** * Un thread care apartine clasei va numara numerele prime dintrun interval specificat. Intervalul este cuprins intre min si max, iar min si max sunt argumentele constructorului. Dupa ce termina de calculat, thread-ul afiseaza un mesaj in care spune cate numere prime a gasit si apoi adauga acel numar la total, apeland metoda addToTotal(int). */ private static class CountPrimesThread extends Thread { int count = 0; int min, max; public CountPrimesThread(int min, int max) { this.min = min; this.max = max; } public void run() { count = countPrimes(min,max); System.out.println("Sunt " + count + " numere prime intre " + min + " si " + max); addToTotal(count); } } /** * Numara numerele prime dintre (start+1) si (2*start) folosind * un anumit numar de thread-uri. Timpul in care se face calculul * este afisat. */ private static void countPrimesWithThreads(int numberOfThreads) { * * * * *

smaranda.belciug@inf.ucv.ro
int increment = start/numberOfThreads; System.out.println("\nNumar numerele prime dintre " + (start+1) + " si " + (2*start) + " folosind " + numberOfThreads + " threaduri...\n"); long startTime = System.currentTimeMillis(); CountPrimesThread[] worker = new CountPrimesThread[numberOfThreads]; for (int i = 0; i < numberOfThreads; i++) worker[i] = new CountPrimesThread( start+i*increment+1, start+(i+1)*increment ); total = 0; for (int i = 0; i < numberOfThreads; i++) worker[i].start(); for (int i = 0; i < numberOfThreads; i++) { while (worker[i].isAlive()) { try { worker[i].join(); } catch (InterruptedException e) { } } } long elapsedTime = System.currentTimeMillis() - startTime; System.out.println("\nTimpul este: " + (elapsedTime/1000.0) + " secunde.\n"); } /** * Primeste numarul de thread-uri de la utilizator si numara numerele * prime folosind acel numar de thread-uri */ public static void main(String[] args) { int numberOfThreads = 0; Scanner in = new Scanner(System.in); while (numberOfThreads < 1 || numberOfThreads > 5) { System.out.print("Cate thread-uri vrei sa folosesti? (intre 1 si 5)? "); numberOfThreads = in.nextInt(); if (numberOfThreads < 1 || numberOfThreads > 5) System.out.println("Te rog sa introduci 1, 2, 3, 4, sau 5 !"); } countPrimesWithThreads(numberOfThreads); } /** * Numara cate numere prime sunt intre min si max */ private static int countPrimes(int min, int max) { int count = 0; for (int i = min; i <= max; i++) if (isPrime(i)) count++; return count; } /** * Testeaza daca x este numar prim sau nu */

10

smaranda.belciug@inf.ucv.ro
private static boolean isPrime(int x) { int top = (int)Math.sqrt(x); for (int i = 2; i <= top; i++) if ( x % i == 0 ) return false; return true; } }

Cate thread-uri vrei sa folosesti? (intre 1 si 5)?

Numar numerele prime dintre 3000001 si 6000000 folosind 5 threaduri... Sunt 39910 numere prime intre 3000001 39910 numere prime gasite pana acum. Sunt 39588 numere prime intre 3600001 79498 numere prime gasite pana acum. Sunt 39125 numere prime intre 4200001 118623 numere prime gasite pana acum. Sunt 38923 numere prime intre 4800001 157546 numere prime gasite pana acum. Sunt 38487 numere prime intre 5400001 196033 numere prime gasite pana acum. Timpul este: 7.621 secunde. 1 si 3600000 si 4200000 si 4800000 si 5400000 si 6000000

Cate thread-uri vrei sa folosesti? (intre 1 si 5)?

Numar numerele prime dintre 3000001 si 6000000 folosind 1 threaduri... Sunt 196033 numere prime intre 3000001 si 6000000 196033 numere prime gasite pana acum. Timpul este: 8.635 secunde.

Sincronizarea poate preveni multe conflicte, dar poate produce alte erori, gen deadlock. Un deadlock apare atunci cnd un thread ateapt pentru totdeauna pentru o resurs pe care nu o va primi nicioadat. Wait i Notify Thread-urile pot s interacioneze i n alte moduri. De exemplu, un thread poate s calculeze un rezultat, care i este necesar altui thread. Acest lucru impune nite reguli cu privire la ordinea n care thread-urile pot s-i fac calculele. Dac cel de-al doilea thread are nevoie de rezultatul primului, dar acesta nu este gata, el poate s intre n modul sleep. n Java exist dou metode pentru a face aceste lucruri: wait() i notify(). Practic, n momentul n care un thread apeleaz metoda wait() pentru un obiect, acel thread intr n modul sleep pn cnd metoda notify() este apelat pentru acelai obiect. Evident metoda va trebui s fie apelat de alt thread, deoarece thread-ul nostru doarme. 11

smaranda.belciug@inf.ucv.ro Deci, un thread A apeleaz wait() n momentul n care are nevoie de un rezultat de la thread-ul B, dar rezultatul respectiv nu este gata nc. Cnd thread-ul B are gata rezultatul, atunci apeleaz notify(), fapt ce va trezi thread-ul A, astfel nct acesta s poat folosi rezultatul respectiv. Pentru a implementa cele scrise mai sus, thread-ul A va trebui s execute un cod de genul:
if ( resultIsAvailable() == false ) obj.wait(); // asteapta notificarea ca rezultatul este gata useTheResult();

n timp ce thread-ul B:
generateTheResult(); obj.notify(); // trimite notificarea ca rezultatul e gata

n acest cod poate s apar un race condition. Cele dou thread-uri pot s execute codul n urmtoarea ordine: 1. Thread-ul A verific resultIsAvailable i afl c rezultatul nu este gata, aa c decide s execute obj.wait(), dar nainte ca el s execute metoda 2. Thread-ul B termin de calculat rezultatul i apeleaza obj.notify() 3. Thread-ul A apeleaz obj.wait() i ateapt notificarea de la B, dar aceasta nu va veni niciodat, deoarece a fost deja apelat. (deadlock). Soluia este s sincronizm thread-urile. Exemplu. S considerm o problem de gen productor/consumator, unde un thread produce un rezultat, care apoi este consumat de un alt thread. S presupunem c avem o variabil comun sharedResult, care este folosit pentru a transfera rezultatul de la un productor la un consumator. n momentul n care rezultatul este gata, productorul seteaz valoarea variabilei ca fiind diferit de null. Vom folosi o variabil lock pentru sincronizare. Codul thread-ului producator va fi:
makeResult = generateTheResult(); // Nu e sincronizat synchronized(lock) { sharedResult = makeResult; lock.notify(); }

n timp ce consumatorul va executa codul:


synchronized(lock) { while ( sharedResult == null ) { try { lock.wait(); } catch (InterruptedException e) { }

12

smaranda.belciug@inf.ucv.ro
} useResult = sharedResult; } useTheResult(useResult); // nu este sincronizat

Se observ c apelurile meotdelor generateTheResult() i useTheResult() nu sunt sincronizate, ceea ce permite s ruleze n paralel cu alte thread-uri care ar putea s fie sincronizate pe lock. Pentru c shareResult este o variabil comun, toate referinele la ea trebuie sincronizate. Exemplu.
import java.util.ArrayList; import java.util.Scanner; /** * Acest program este identic cu ThreadTest2. Diferenta consta * in faptul ca aici folosim modelul producator/consumator, pentru * a prelua rezultatele de la thread-uri. */ public class ThreadTest3 { private static final int start = 3000000; /** * Un obiect care este folosit sa transfere rezultate de la * thread-urile care fac calcule. Un intreg x este adunat la results * prin apelarea metodei results.produce(x). Un rezultat este preluat * folosind metoda results.consume(). Daca results.consume() este apelat * atunci cand nu este niciun rezultat valabil, va astepta pana cand * unul va deveni valabil. */ private static ProducerConsumer results = new ProducerConsumer();

/** * Un obiect de tip ProducerConsumer reprezinta o lista de rezultate * care sunt valabile pentru procesare. Rezultatele sunt adaugate * la o lista prin apelarea metodei produce si scoase prin apelarea lui * consume. * Daca niciun rezultat nu este valabil, metoda nu va returna nimic * pana cand un rezultat va fi valabil. */ private static class ProducerConsumer { private ArrayList<Integer> items = new ArrayList<Integer>(); public void produce(int n) { synchronized(items) { items.add(n); // adauga n la lista de rezultate items.notify();

13

smaranda.belciug@inf.ucv.ro
// notifica orice thread care asteapta sa apeleze consume() } } public int consume() { int n; synchronized(items) { //daca nu este niciun rezultat valabil, asteapta notificarea de la //produce() while (items.size() == 0) { try { items.wait(); } catch (InterruptedException e) { } } // in acest moment cel putin un rezultat e gata n = items.remove(0); } return n; } } /** * dupa ce obtine un rezultat il adauga la results prin apelarea * results.produce() */ private static class CountPrimesThread extends Thread { int count = 0; int min, max; public CountPrimesThread(int min, int max) { this.min = min; this.max = max; } public void run() { count = countPrimes(min,max); System.out.println("Sunt " + count + " numere prime intre " + min + " si " + max); results.produce(count); } }

private static void countPrimesWithThreads(int numberOfThreads) { int increment = start/numberOfThreads; System.out.println("\nNumar numere prime intre " + (start+1) + " si " + (2*start) + " folosind " + numberOfThreads + " threaduri...\n"); long startTime = System.currentTimeMillis(); CountPrimesThread[] worker = new CountPrimesThread[numberOfThreads]; for (int i = 0; i < numberOfThreads; i++) worker[i] = new CountPrimesThread( start+i*increment+1, start+(i+1)*increment ); for (int i = 0; i < numberOfThreads; i++) worker[i].start(); int total = 0; for (int i = 0; i < numberOfThreads; i++) {

14

smaranda.belciug@inf.ucv.ro
//adauga toate rezultatele de la thread-uri. stim cate thread-uri //sunt, deci stim la cate rezultate sa ne asteptam. programul //se va opri in momentul in care va primi numarul respectiv de rezultate total = total + results.consume(); } long elapsedTime = System.currentTimeMillis() - startTime; System.out.println("\nAm gasit " + total + " numere prime."); System.out.println("\nTimp: " + (elapsedTime/1000.0) + " secunde.\n"); }

public static void main(String[] args) { int numberOfThreads = 0; Scanner in = new Scanner(System.in); while (numberOfThreads < 1 || numberOfThreads > 5) { System.out.print("Cate thread-uri vrei? (intre 1 si 5) ? "); numberOfThreads = in.nextInt(); if (numberOfThreads < 1 || numberOfThreads > 5) System.out.println("Introdu 1, 2, 3, 4, sau 5 !"); } countPrimesWithThreads(numberOfThreads); } private static int countPrimes(int min, int max) { int count = 0; for (int i = min; i <= max; i++) if (isPrime(i)) count++; return count; }

private static boolean isPrime(int x) { int top = (int)Math.sqrt(x); for (int i = 2; i <= top; i++) if ( x % i == 0 ) return false; return true; } } Cate thread-uri vrei? (intre 1 si 5) ? 5

Numar numere prime intre 3000001 si 6000000 folosind 5 thread-uri... Sunt Sunt Sunt Sunt Sunt 39910 39588 39125 38923 38487 numere numere numere numere numere prime prime prime prime prime intre intre intre intre intre 3000001 3600001 4200001 4800001 5400001 si si si si si 3600000 4200000 4800000 5400000 6000000

Am gasit 196033 numere prime. Timp: 6.943 secunde.

15

smaranda.belciug@inf.ucv.ro Variabile volatile n general, thread-urile comunic folosind variabile comune i accesnd acele variabile n metode sincronizate. Totui folosirea sincronizrii este destul de scump din punct de vedere computaional. Dac o varibil comun este setat de un thread i folosit de altul, este posibil, din cauza modului n care au fost implementate thread-urile n Java, ca cel de-al doilea thread s nu sesizeze modificarea valorii variabilei i s lucreze n continuare cu cea veche. De ce? Din cauza faptului c fiecare thread are dreptul s-i pstreze o copie locala a variabilei comune (to cache shared data). Astfel, cnd se modifica valoarea de un thread, variabila locala a altuia nu se modific imediat. Soluia? Folosirea unei variabile comune (n afara codului sincronizat) care s fie volatil. Cuvntul volatile este un modificator care poate fi adugat n declararea unei variabile astfel:
private volatile int count;

Dac variabila este declarat ca fiind volatile, niciun thread nu va pstra o copie local a variabilei n cache-ul su. Thread-ul va folosi varianta oficial a variabilei. Asta nseamn ca orice schimbarea a variabilei va fi valabil instantaneu tuturor thread-urilor. Exemplu. S trimitem un semnal de la un thread la altul n care s cerem ca cel de-al doilea s termine lucrul. Thread-urile ar avea o variabil comun:
volatile boolean terminate = false;

Metoda run a celui de-al doilea thread ar trebui s verifice valoarea lui terminate frecvent i s termine lucrul cnd aceasta devine true.
public void run() { while (true) { if (terminate) return; . . // lucreaza . } }

16