[Java] Problema thread - locks

Cunoscutul limbaj de programare Java a fost creat să întrunească câteva caracteristici de bază, printre care OOP, să fie independent de arhitectură (portabilitatea), să fie dinamic şi securizat. Aici vom vorbi despre Java SE, Java ME, respectiv Java EE. Dacă acesta este domeniul tău de interes, aceasta este secţiunea potrivită pentru tine.

[Java] Problema thread - locks

Postby smith » 14 Mar 2011, 18:31

Recent m-am jucat puțin cu threadurile în Java dar am dat peste niște probleme.

Am scris următorul cod care crează două threaduri (unul care scrie -producer- și unul care citește -consumer-). Am încercat să fac în așa fel încât să nu se intercaleze citirile și scrierile (sper că codul este destul de clar).

Vreau ca cele două threaduri să scrie și să citească date din buffer alternativ. Am făcut acest lucru prin variabila "available". Când această variabilă este true, metoda get() poate să returneze datele - altfel threadul așteaptă până sunt puse date. Când este false, înseamnă că metoda put(int) poate să scrie date - altfel, threadul este așteaptă până sunt luate datele de către celălalt thread cu get().

  1. class Buffer{
  2.     private int data = -1;
  3.     private boolean available = false;
  4.    
  5.     public synchronized int get(){
  6.         while(!available){
  7.             try{
  8.                 wait();
  9.             } catch(Exception e) { e.printStackTrace(); }
  10.         }
  11.         available = false;
  12.         notifyAll();
  13.         return data;
  14.     }
  15.    
  16.     public synchronized void put(int data){
  17.         while(available){
  18.             try{
  19.                 wait();
  20.             } catch (Exception e) { e.printStackTrace(); }
  21.         }
  22.         this.data= data;   
  23.         available = true;
  24.         notifyAll();
  25.     }
  26. }
  27.  
  28. class Producer extends Thread {
  29.    
  30.     private Buffer buffer;
  31.    
  32.     public Producer(Buffer buffer){
  33.          this.buffer = buffer;
  34.     }
  35.     public void run(){
  36.         for(int i=0;i<10;i++){
  37.             buffer.put(i);
  38.             System.out.println("Producer a pus "+ i);
  39.         }
  40.     }
  41. }
  42.  
  43. class Consumer extends Thread {
  44.    
  45.     private Buffer buffer;
  46.    
  47.     public Consumer(Buffer buffer){
  48.          this.buffer = buffer;
  49.     }
  50.     public void run(){
  51.         for(int i=0;i<10;i++){
  52.             System.out.println("Consumer a scos " + buffer.get());
  53.         }
  54.     }
  55. }
  56.  
  57.  
  58. public class HelloWorld {
  59.     public static void main(String args[]){
  60.        
  61.         Buffer b = new Buffer();
  62.        
  63.         Producer producer = new Producer(b);
  64.         Consumer consumer = new Consumer(b);
  65.        
  66.         producer.setPriority(Thread.MAX_PRIORITY);
  67.         consumer.setPriority(Thread.MAX_PRIORITY);
  68.        
  69.         producer.start();
  70.         consumer.start();
  71.     }
  72. }


Cu toate acestea, la mai multe rulări, apar greșeli relativ frecvent în rezultate (liniile 13-14 și 17-18):
  1. Producer a pus  0
  2. Consumer a scos 0
  3. Producer a pus  1
  4. Consumer a scos 1
  5. Producer a pus  2
  6. Consumer a scos 2
  7. Producer a pus  3
  8. Consumer a scos 3
  9. Producer a pus  4
  10. Consumer a scos 4
  11. Producer a pus  5
  12. Consumer a scos 5
  13. Consumer a scos 6
  14. Producer a pus  6
  15. Producer a pus  7
  16. Consumer a scos 7
  17. Consumer a scos 8
  18. Producer a pus  8
  19. Producer a pus  9
  20. Consumer a scos 9


De ce se întâmplă acest lucru?
0,0p / 0 votes
Ilea Cristian
User avatar
smith
Enum
 
Joined: 29 Dec 2009
Location: Cluj-Napoca
Status: 82

Re: [Java] Problema thread - locks

Postby xlad » 14 Mar 2011, 19:41

Dupa ce iese din functia buffer.put(i), procesorul probabil suspenda executia threadului producer pentru ca i-a expirat cuanta de timp(time slice) si continua cu executia lui consumer. Consumer vede ca a fost schimbat acel flag si apoi afiseaza ca a reusit sa scoata.

Exemplu de executie al procesorului :
//running Producer thread
i = 4;
buffer.put(4);
System.out.println("Producer a pus 4");
i = 5;
buffer.put(5);
System.out.println("Producer a pus 5");
i = 6;
buffer.put(6);
//switch to consumer thread because producer threads' time slice expired
i=4;
System.out.println("Consumer a scos 4");
i=5;
System.out.println("Consumer a scos 5");
i=6;
System.out.println("Consumer a scos 6");
i=7;
Wait
//switch to producer thread
System.out.println("Producer a pus 6");
0,0p / 0 votes
User avatar
xlad
Bit
 
Joined: 06 Jan 2010
Status: 2

Re: [Java] Problema thread - locks

Postby smith » 15 Mar 2011, 21:48

Nu mă lămurește răspunsul.

LaterEdit: Nu am avut timp să argumentez de ce nu sunt de acord cu explicația ta. Cel puțin exemplul tau nu mi se pare corect, sau cel puțin nu l-am înțeles eu.

Acum ceea ce am dedus eu:

Cu toate că am sincronizat functiile și am pus flagul astfel încât să nu se intersecteze scrierile și citirile, nu afișa (!) rezultatul bun în toate cazurile. În schimb, threadurile nu se "intersectau". Deci doar afișarea era o problemă și ele scriau și citeau date în ordinea în care eu am vrut.

Problema pe care am bănuit-o eu (și pe care inițial nu am vrut să o cred, nefiind obișnuit cu o astfel de gândire probabil) ar fi că:

După ce metoda put() s-a terminat și a și setat flagul că e ok să se citească datele cu get(), se poate întâmpla cum a zis și xlad ca threadul producer să se oprească și să se treacă la threadul consumer care vede că se pot citi date, fără ca threadul producer să ajungă să afișeze ceea ce el a inserat în buffer. Apoi producer afișează ce a inserat doar după ce consumer scoate deja datele și intră în wait().

@xlad, cam asta ai spus și tu doar că nu am înțeles exemplul tău. După mine, ar fi arătat așa:
Să presupunem că threadul consumer începe primul să ruleze (pentru că totul este relativ):

  1. thread -> Consumer
  2. i:=0
  3. available? nu->wait() //suspenda executia threadului
  4. thread -> Producer
  5. i:=0
  6. available? nu-> put(i) //0
  7. set flag available:=true
  8. nofity all
  9. //threadul este suspendat - procesorul decide suspendarea acestui thread
  10. thread -> Consumer
  11. // i încă este 0 - pentru că threadul a fost suspendat de wait()
  12. available? da -> get()
  13. setează flag available:=false
  14. notify all
  15. afișează că a scos 0
  16. i++ //1
  17. available? nu-> wait()
  18. thread -> Producer
  19. afișează că a inserat 0 //reia de unde a rămas
  20. i++ //1
  21. available? nu -> put(i) //1
  22. set flag available:=true
  23. notify all
  24. afisează că a inserat 1 //de data aceasta nu mai este întrerupt
  25. i++
  26. available? da -> wait()
  27. thread-> Consumer
  28. ... and so on...


Soluția mea ar fi următoarea:
Clasa buffer arată așta (am adăugat o metodă specială pentru a seta flagul):
  1. class Buffer{
  2.     private int data = -1;
  3.     private boolean available = false;
  4.    
  5.     public synchronized int get(){
  6.         while(!available){
  7.             try{
  8.                 wait();
  9.             } catch(Exception e) { e.printStackTrace(); }
  10.         }
  11.         return data;
  12.     }
  13.    
  14.     public synchronized void put(int data){
  15.         while(available){
  16.             try{
  17.                 wait();
  18.             } catch (Exception e) { e.printStackTrace(); }
  19.         }
  20.         this.data = data;   
  21.     }
  22.    
  23.     public synchronized void setAvailable(boolean flag){
  24.         available = flag;
  25.         notifyAll();
  26.     }
  27. }

Metoda run() din Producer (setez flagul DOAR după ce threadul a afișat ce trebuie):
  1. public void run(){
  2.     for(int i=0;i<10;i++){
  3.         buffer.put(i);
  4.         System.out.println("Producer a pus "+ i);
  5.         buffer.setAvailable(true);
  6.     }
  7. }

La fel am făcut și în metoda run() din Consumer:
  1. public void run(){
  2.     for(int i=0;i<10;i++){
  3.         System.out.println("Consumer a scos " + buffer.get());
  4.         buffer.setAvailable(false);
  5.     }
  6. }


Totuși, aștept o aprobare!
0,0p / 0 votes
Ilea Cristian
User avatar
smith
Enum
 
Joined: 29 Dec 2009
Location: Cluj-Napoca
Status: 82

Re: [Java] Problema thread - locks

Postby DarkByte » 15 Mar 2011, 21:52

Da, cam asa ar trebui sa fie pentru a sincroniza si afisarile.

smith wrote:Problema pe care am bănuit-o eu (și pe care inițial nu am vrut să o cred, nefiind obișnuit cu o astfel de gândire probabil) ar fi ...

Get used to this kind of thinking, when using threads :)) (e unul din locurile cele mai potrivite pentru zicale de-ale lui Murphy ... like "if anything can go wrong, it will" :P)
0,0p / 0 votes
User avatar
DarkByte
11011011
 
Joined: 29 Dec 2009
Status: 140

Re: [Java] Problema thread - locks

Postby morpheus » 15 Mar 2011, 22:38

http://img23.imageshack.us/i/racem.png/
:D

LE: e ok solutia. Poti folosi pentru genul asta de probleme si o clasa precum ArrayBlockingQueue (vezi metodele put si take), sau alta clasa ce implementeaza interfata BlockingQueue
0,0p / 0 votes
User avatar
morpheus
Word
 
Joined: 30 Dec 2009
Location: Bucharest, Romania
Status: 54.84

Re: [Java] Problema thread - locks

Postby smith » 15 Mar 2011, 23:39

^^Am observat, dar e foarte interesant :D. Totuși, chestia e că exemplul era dintr-o carte scrisă în română (luată de aici). Codul postat mai sus de mine (cel greșit) e aproximativ la fel cu cel din carte. Eu am luat de bun exemplul, și am încercat să fac ceva mai diferit și am fost uimit să văd că nu merge - de fapt, nu mergea exemplul din carte (deci aviz cititorilor, să se uite de două ori!).

^ Altă dată o să desenez diagrame, pare mult mai natural de urmărit. Thanks!

@xlad: Sper să te mai prind pe forum...că altfel!
0,0p / 0 votes
Ilea Cristian
User avatar
smith
Enum
 
Joined: 29 Dec 2009
Location: Cluj-Napoca
Status: 82


Return to Java

Who is online

Users browsing this forum: No registered users and 0 guests