documentatie java threads

6
11/20/2015 Documentatie Java threads http://www.cs.ubbcluj.ro/~forest/HtmlFolder/PDPJ/threads.html#thrsynchr 1/6 Threaduri simple in Java | Sincronizarea threadurilor Threaduri simple in Java In Java putem crea threaduri fie extinzand (mostenind, derivand din) clasa Thread , fie implementand interfata Runnable . Esential este ca clasa noastra thread sa aiba o metoda public void run() care constituie corpul principal de instructiuni care se va executa in cadrul threadului. Un exemplu simplu de program care foloseste threaduri java este urmatorul. Acest program creeaza 10 threaduri si fiecare thread isi va tipari la pornire si la oprire propriul ID (un numar unic alocat threadului la creare). import java.util.*; class AThread extends Thread { int id; AThread() { id = 0;} AThread(int id) { this.id = id;} public void run() { Random rand = new Random(); System.out.println("Thread "+id+" starting .."); try { sleep(((int)(1+ Math.random()))*10000); } catch (Exception ex) { System.err.println("exception caught while sleeping"); } System.out.println("Thread "+id+" exiting.."); } } public class ThrEx { public static void main(String args[]) { AThread threads[] = new AThread[10]; for(int i=0; i<10; i++) threads[i] = new AThread(i); for(int i=0; i<10; i++) threads[i].start(); } } Threadurile java, fie ca sunt create mostenind de la clasa Thread sau implementand interfata Runnable , se pornesc cu metoda start() si se opresc cu metodele stop() si join(). Sincronizarea threadurilor Conceptul de thread (fir de executie) este folosit in programare pentru a eficientiza executia programelor, executand portiuni distincte de cod in paralel, in interiorul aceluiasi proces. Cateodata insa, aceste portiuni de cod care constituie corpul threadurilor, nu sunt complet independente si in anumite momente ale executiei, se poate intampla ca un thread sa trebuiasca sa astepte executia unor instructiuni din alt thread, pentru a putea continua executia propriilor instructiuni. Aceasta tehnica prin care un thread asteapta executia altor threaduri inainte de a continua propria executie, se numeste sincronizarea threadurilor. Java ofera urmatoarele facilitati pentru sincronizarea threadurilor: mecanismul synchronized si metodele: wait, notify si notifyAll. Pentru a intelege problematica sincronizarii threadurilor, vom considera problema producatorilor si a consumatorilor. Aceasta spune ca avem mai multi producatori care "produc" in paralel obiecte si le depoziteaza intrun container comun si avem mai multi consumatori care "consuma" in acelasi

Upload: alice-state

Post on 01-Feb-2016

213 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Documentatie Java Threads

11/20/2015 Documentatie Java threads

http://www.cs.ubbcluj.ro/~forest/HtmlFolder/PDPJ/threads.html#thrsynchr 1/6

Threaduri simple in Java | Sincronizarea threadurilor

Threaduri simple in Java In Java putem crea threaduri fie extinzand (mostenind, derivand din) clasa Thread, fie implementand interfataRunnable. Esential este ca clasa noastra thread sa aiba o metoda public void run() care constituie corpul principalde instructiuni care se va executa in cadrul threadului. Un exemplu simplu de program care foloseste threadurijava este urmatorul. Acest program creeaza 10 threaduri si fiecare thread isi va tipari la pornire si la oprire propriulID (un numar unic alocat threadului la creare). 

import java.util.*;

class AThread extends Thread {  int id;  AThread() { id = 0;}  AThread(int id) { this.id = id;}   public void run() {     Random rand = new Random();    System.out.println("Thread "+id+" starting ..");    try {       sleep(((int)(1+ Math.random()))*10000);    } catch (Exception ex) {      System.err.println("exception caught while sleeping");     }     System.out.println("Thread "+id+" exiting..");  }}

public class ThrEx {  public static void main(String args[]) {     AThread threads[] = new AThread[10];    for(int i=0; i<10; i++)      threads[i] = new AThread(i);    for(int i=0; i<10; i++)      threads[i].start();   } }

Threadurile java, fie ca sunt create mostenind de la clasa Thread sau implementand interfata Runnable, se pornesccu metoda start() si se opresc cu metodele stop() si join().

Sincronizarea threadurilorConceptul de thread (fir de executie) este folosit in programare pentru a eficientiza executia programelor,executand portiuni distincte de cod in paralel, in interiorul aceluiasi proces. Cateodata insa, aceste portiuni de codcare constituie corpul threadurilor, nu sunt complet independente si in anumite momente ale executiei, se poateintampla ca un thread sa trebuiasca sa astepte executia unor instructiuni din alt thread, pentru a putea continuaexecutia propriilor instructiuni. Aceasta tehnica prin care un thread asteapta executia altor threaduri inainte de acontinua propria executie, se numeste sincronizarea threadurilor. Java ofera urmatoarele facilitati pentrusincronizarea threadurilor:

mecanismul synchronizedsi metodele: wait, notify si notifyAll. Pentru a intelege problematica sincronizarii threadurilor, vom considera

problema producatorilor si a consumatorilor. Aceasta spune ca avem mai multi producatori care "produc" inparalel obiecte si le depoziteaza intr­un container comun si avem mai multi consumatori care "consuma" in acelasi

Page 2: Documentatie Java Threads

11/20/2015 Documentatie Java threads

http://www.cs.ubbcluj.ro/~forest/HtmlFolder/PDPJ/threads.html#thrsynchr 2/6

timp obiectele depozitate in container de catre producatori. Toti producatorii si consumatorii vor partaja acelasicontainer. Pentru a simplifica putin lucrurile am ales ca container care va fi partajat de producatori si consumatori,o clasa Product care incapsuleaza o valoare de tip int. Astfel ca producatorii si consumatorii vor produce,respectiv consuma, valori de tip int. Ca sa fie si mai simplu, containerul (obiect de tipul Product) poate contine osingura valoare de tipul int (poate contine un singur produs), iar la un moment dat exista un singur consumator siun singur producator in executie. Pornind de la aceste reguli, am scris urmatorul program pentru problemaproducatorilor si consumatorilor (producatorii si consumatorii sunt evident threaduri): 

class Consumer extends Thread {  private Product prod;     public Consumer(Product prod) {        this.prod = prod;    }

    public void run() {        int value = 0;        for (int i = 0; i < 10; i++) {            value = prod.get();                                 try {                sleep(1+(int)(Math.random() * 100));            } catch (InterruptedException e) { }                    }    }}

class Producer extends Thread {    private Product prod;

    public Producer(Product prod) {              this.prod = prod;    }

    public void run() {        for (int i = 0; i < 10; i++) {          prod.set(i);                     try {                sleep(1+(int)(Math.random() * 100));            } catch (InterruptedException e) { }        }    }}

class Product {  private int value;  private boolean available;    // daca sunt produse in container  Product() {value = ‐1; available = false;}    public boolean empty() { return !available; }   public int get() {        // consuma produs    while (available==false) {      try {        Thread.currentThread().sleep(5);      } catch(Exception ex) { System.err.println("error sleeping");}    }       available = false;    System.out.println("am consumat produsul "+value);    return value;   }  public void set(int val) {       // produce produs

Page 3: Documentatie Java Threads

11/20/2015 Documentatie Java threads

http://www.cs.ubbcluj.ro/~forest/HtmlFolder/PDPJ/threads.html#thrsynchr 3/6

    while (available==true) {      try {        Thread.currentThread().sleep(6);      } catch(Exception ex) { System.err.println("error sleeping");}         }

    available = true;    value = val;    System.out.println("am produs "+val);  }}

public class Thrsynchr {  public static void main(String args[]) {    Product prod = new Product();    Consumer c = new Consumer(prod);    Producer p = new Producer(prod);    p.start();    c.start();   }}

In programul precedent se folosesc metodele get() si set() din clasa Product pentru a consuma, respectiv produce,o valoare de tipul int. Unul dintre bug­urile care pot aparea in cazul in care modific programul in asa fel incat saavem 2 consumatori care sa consume in paralel din acelasi container (care poate contine maxim o valoare de tipulint) si un singur producator care sa puna cate o valoare in acel container, este situatia in care cei doi consumatoriconsuma aceeasi valoare produsa o singura data de catre producator. Acesta este un bug­urile complicat, deoareceaparitia lui este aleatoare, drept urmare, el poate fi reprodus destul de greu. Situatia este perfect plauzibila daca negandim putin. Sa ne gandim ca initial containerul e gol. Consumatorul 1 tot executa ciclul while(available==false) { ... } din metoda Product.get() deoarece nu exista nici o valoare de consumat. La fel siConsumatorul 2 este blocat de ciclul while pana producatorul va produce o valoare. Cat timp cei doi consumatorisunt in ciclul while, threadul Producator produce o valoare. Sa presupunem ca primul care va iesi din while va fiConsumatorul 1. Consumatorul 1 va consuma valoarea din container, dar sa presupunem ca inainte de a setaavailable la false, Consumatorul 2 iese si el din while si incepe si el sa consume acceasi valoare pe care oconsuma Consumatorul 1. In cazul exemplului nostru simplu, e drept, aceasta situatie poate avea loc mai rar(totusi ea se poate intampla!), dar in cazul programelor mai complexe care contin mai multe linii de cod intrepartea de verificare a disponibilitatii datelor (ciclul wait) si partea de consum efectiv a valorii (available=false),acest bug poate aparea suparator de frecvent. 

Eliminarea bug­ului ar consta intr­un mecanism care ar face tot codul din metoda Product.get() atomic. Astfel,daca as avea la dispozitie un mecanism care ar oferi garantia ca atunci cand metoda get() a inceput sa se execute ininteriorul unui thread, nici un alt thread in afara de cel curent nu va putea executa vreo instructiune pana nu arerost iesirea din metoda get, bug­ul mentionat mai sus va fi eliminat cu certitudine. Acest mecanism de asigurare aatomicitatii (mecanism de blocare) este introdus in Java prin cuvantul rezervat synchronized. In Java pot sasincronizez (sa blochez, sa fac atomic) o metoda sau o portiune de cod. Incepem cu metodele sincronizate si apoivorbim si de portiuni de cod sincronizat.Adaugarea cuvantului rezervat synchronized in fata unei metode face ca in timpul executiei, la un moment dat,aceasta metoda sa nu poata fi executata pe acelasi obiect decat de un singur thread. Altfel spus, un singur threadpoate executa aceasta metoda la un moment dat. Este important de notat faptul ca, o metoda synchronized poate fiapelata in acelasi timp pe doua obiecte diferite, dar daca o apelam pe acelasi obiect, ea nu se poate executasimultan in doua threaduri. De ce se intampla acest lucru? Datorita modului in care masina virtuala java (JVM)sincronizeaza metodele. Ce se intampla de fapt la executia unei metode synchronized?Orice obiect (instanta a unei clase) din cadrul masinii virtuale java are asociat un "blocaj invizibil" (lock ­ puteti sava ganditi la acest lock ca la o variabila boolean privata a obiectului care ia valoarea true cand obiectul estefolosit/blocat si false cand nu este folosit/deblocat). Cand apelez o metoda a unei clase totdeauna o apelez pe unanumit obiect (exceptie cazul in care metoda este statica, dar aici ne referim numai la metode nestatice). Candapelez o metoda synchronized intr­un thread, masina virtuala java incearca intai sa obtina blocajul (lock) peobiectul pe care este apelata metoda. Daca acest obiect este blocat deja (de catre alt apel de metoda synchronized

Page 4: Documentatie Java Threads

11/20/2015 Documentatie Java threads

http://www.cs.ubbcluj.ro/~forest/HtmlFolder/PDPJ/threads.html#thrsynchr 4/6

in alt thread), threadul va intra in asteptare pana cand obiectul va fi deblocat. Daca obiectul nu este blocat,threadul curent va bloca obiectul, va rula metoda synchronized, iar apoi va debloca obiectul.

Vom face acum cateva observatii relative la mecanismul synchronized.1. O metoda sincronizata apelata pe doua obiecte diferite, in doua threaduri diferite, nu va sincroniza cele douathreaduri. De ce? Pentru ca blocajele (lock­ul) se va obtine independent pe doua obiecte diferite.2. Blocarea se face totdeauna pe obiectul fizic si nu pe referinte la acesta. Astfel ca, nu este bine sa se modificeobiectul dupa care se sincronizeaza, in interiorul codului sincronizat (acest sfat are sens in cazul portiunilor de codsincronizat si nu in cazul metodelor sincronizate ­ vezi mai jos) 3. Metodele sincronizate se scriu, in general, inafara claselor Thread (cum este cazul clasei Product din exemplul nostru). De ce? 

Pe langa metode sincronizate, mai pot exista portiuni de cod sincronizat. O portiune de cod sincronizat se scrie infelul urmator:

        ....        synchronized(ob) {  //instructiuni  ....        }        ....

In exemplul de mai sus, blocajul se va face pe obiectul ob si va dura pe toata durata codului din interiorul bloculuisynchronized.O varianta si mai speciala de cod sincronizat este sincronizarea dupa clasa, ca in exemplul urmator:

        ....        synchronized(Class.forName("MyClass")) {  //instructiuni  ....        }        ....

In acest exemplu, un singur obiect (nu numai o singura metoda!) de tipul MyClass poate fi folosit (apelabil) la unmoment dat de catre un singur thread. Acest tip de sincronizare (dupa clasa) este cea mai dura sincronizare si facecodul aproape serial (un singur thread se executa la un moment dat si toate celelalte asteapta), astfel ca faceineficienta utilizarea threadurilor.Dupa ce am invatat de mecanismul synchronized, putem sa scriem o varianta mai buna a programului in caremetodele get() si set() din clasa Product sunt sincronizate:

class Consumer extends Thread {    private Product prod;     public Consumer(Product prod) {        this.prod = prod;    }

    public void run() {        int value = 0;        for (int i = 0; i < 10; i++) {            value = prod.get();                                 try {                sleep(1+(int)(Math.random() * 100));            } catch (InterruptedException e) { }                    }    }}

class Producer extends Thread {    private Product prod;

Page 5: Documentatie Java Threads

11/20/2015 Documentatie Java threads

http://www.cs.ubbcluj.ro/~forest/HtmlFolder/PDPJ/threads.html#thrsynchr 5/6

    public Producer(Product prod) {              this.prod = prod;    }

    public void run() {        for (int i = 0; i < 10; i++) {          prod.set(i);                     try {                sleep(1+(int)(Math.random() * 100));            } catch (InterruptedException e) { }        }    }}

class Product {  private int value;  private boolean available;    // daca sunt produse in container  Product() {value = ‐1; available = false;}    public boolean empty() { return !available; }   public synchronized int get() {       // consuma produs    while (available==false) {      try {        Thread.currentThread().sleep(5);        //wait(5000);      } catch(Exception ex) { System.err.println("error sleeping");}    }       available = false;    System.out.println("am consumat produsul "+value);    //notifyAll();    return value;   }  public synchronized void set(int val) {   // produce produs    while (available==true) {      try {        Thread.currentThread().sleep(6);        //wait(6000);      } catch(Exception ex) { System.err.println("error sleeping");}         }

    available = true;    value = val;    System.out.println("am produs "+val);    //notifyAll();   }}

public class Thrsynchr {  public static void main(String args[]) {    Product prod = new Product();    Consumer c = new Consumer(prod);    Producer p = new Producer(prod);    p.start();    c.start();   }}

Daca vom rula programul de mai sus, vom observa insa ca, la un moment dat, chiar daca executia nu s­a terminat,programul nu mai afiseaza nimic. Se intampla ceea ce se numeste deadlock, adica doua threaduri asteapta lainfinit unul dupa celalalt, ca sa­si poata continua executia, fara insa a reusi sa execute instructiuni nici unul dintrethreaduri. De ce apare deadlock­ul? Este foarte simplu. Sa presupunem ca containerul este initial gol.

Page 6: Documentatie Java Threads

11/20/2015 Documentatie Java threads

http://www.cs.ubbcluj.ro/~forest/HtmlFolder/PDPJ/threads.html#thrsynchr 6/6

Consumatorul va bloca obiectul prod si va executa metoda get(). El insa se va opri in ciclul while(available==false) {...} deoarece nu este inca nici un produs in container. Threadul Producator incearca in zadarsa produca o valoare, apeland metoda set() deoarece pentru a putea apela aceasta metoda, threadul trebuie sablocheze acelasi obiect prod care este blocat de catre threadul Consumator. Astfel ca threadul Consumator vaastepta in ciclul while dupa threadul Producator sa produca o valoare, in timp ce threadul Producator nu poateproduce o valoare pentru ca nu poate sa obtina blocajul pe obiectul prod (blocaj detinut de threadul Consumator).Solutia este inlocuirea apelului sleep() in metodele get() si set() cu metoda wait(). Metoda wait() face aproximativacelasi lucru ca si metoda sleep, cu doua diferente esentiale:metoda wait elibereaza blocajul (lock­ul) pe obiect, spre deosebire de metoda sleep() care nu elibereaza

blocajul. Practic, prin aceasta eliberare a blocajului pe care o face wait(), alte threaduri care asteapta dupa blocajulaceluiasi obiect, pot sa intre in lucru, evitand astfel deadlock­ul. Incercati sa rulati programul cu wait() in loc desleep() si veti vedea ca el se va termina cu succes si nu va mai ajunge la deadlock.metoda wait() se poate "trezi" mai repede decat este specificat in parametrul de timeout, "dormind" astfel un

numar variabil de secunde, spre deosebire de metoda sleep care "doarme" intotdeauna un numar fix de secunde.Metoda wait() se poate trezi mai repede decat este specificat prin parametru, daca un alt thread apeleaza metodanotify() sau notifyAll().

Un alt element demn de observat este ca metoda sleep() tine de clasa Thread, pe cand metoda wait() tine de clasaObject, radacina ierarhiei de clase din limbajul Java. Metodele notify() si notifyAll() "trezesc" threadurile careasteapta (wait()) dupa o anumita conditie. Care este aceasta conditie? Conditia este implicita si se poate descrieprin secventa: "obtinerea blocajului asupra unui obiect". Pentru a observa efectul metodelor notify() si notifyAll(),rulati programul asa cum este prezentat el mai sus, iar apoi, decomentati si apelurile notifyAll() din metodele get()si set() si rulati din nou programul. Veti vedea ca a doua rulare are un timp de executie mai redus decat prima.Acest lucru se intampla pentru ca "wait­urile" nu "dorm" pana la capat pentru ca sunt "trezite" de "notify­uri". Oultima observatie: metodele notify() si notifyAll() sunt tot din clasa Object si nu din clasa Thread.