c3_canale

16
Comunicarea prin canale Modelul clasic Ne ocupăm în continuare de situaţia când excludem o memorie comună ca posibilitate de schimb de informaţii între procese. Varianta luată în considerare în continuare este ca acest schimb de informaţii să se facă prin transmitere de informaţii (mesaje) de la un proces la altul. Există o mare varietate de posibilităţi de transmitere a mesajelor între procese (fire în Java). Se disting trei grade principale de sincronizare: 1) Lipsa sincronizării (transmiterea asincronă). Sursa emite mesaje fără să se intereseze dacă precedentele mesaje au ajuns sau nu la destinaţie. Inconvenientul evident este acela că atunci când la destinaţie ajunge un mesaj, destinatarul nu ştie dacă este sau nu vorba de ultima informaţie plecată de la sursă. Similar, atunci când transmite un mesaj, sursa nu cunoaşte câte din precedentele mesaje au ajuns la destinaţie, deci nu cunoaşte gradul de informare al destinatarului. Transmiterea asincronă poate fi comparată cu corespondenţa prin poştă. 2) Sincronizarea simplă. După ce emiţătorul a transmis un mesaj, el nu va mai trimite altul (va aştepta) până când mesajul va fi recepţionat de destinatar. Aceasta presupune că sursa este informată dacă destinatarul a preluat mesajul. Spunem că are loc o întâlnire (un rendez-vous), imaginându-ne că emiţătorul se întâlneşte cu destinatarul pentru a-i înmâna mesajul. Transmiterea unor informaţii prin fax poate fi încadrată în acest mod de sincronizare: emiţătorul primeşte imediat o confirmare din partea destinatarului că a primit mesajul. 3) Invocarea la distanţă (rendez-vous extins). În plus faţă de sincronizarea simplă, emiţătorul primeşte nu numai confirmarea de recepţie, dar şi un răspuns din partea destinatarului; abia după aceea emiţătorul poate transmite un alt mesaj. În continuare ne vom rezuma la sincronizarea simplă. Sincronizarea simplă are la bază lucrul cu canale. Afirmăm că: 1 Utilizarea canalelor reprezintă modalitatea cea mai naturală pentru rezolvarea problemei sincronizării.

description

cursuri Java

Transcript of c3_canale

Page 1: c3_canale

Comunicarea prin canale

Modelul clasicNe ocupăm în continuare de situaţia când excludem o memorie comună ca posibilitate de

schimb de informaţii între procese. Varianta luată în considerare în continuare este ca acest schimb de informaţii să se facă prin transmitere de informaţii (mesaje) de la un proces la altul.

Există o mare varietate de posibilităţi de transmitere a mesajelor între procese (fire în Java). Se disting trei grade principale de sincronizare:

1) Lipsa sincronizării (transmiterea asincronă). Sursa emite mesaje fără să se intereseze dacă precedentele mesaje au ajuns sau nu la destinaţie. Inconvenientul evident este acela că atunci când la destinaţie ajunge un mesaj, destinatarul nu ştie dacă este sau nu vorba de ultima informaţie plecată de la sursă. Similar, atunci când transmite un mesaj, sursa nu cunoaşte câte din precedentele mesaje au ajuns la destinaţie, deci nu cunoaşte gradul de informare al destinatarului. Transmiterea asincronă poate fi comparată cu corespondenţa prin poştă.

2) Sincronizarea simplă. După ce emiţătorul a transmis un mesaj, el nu va mai trimite altul (va aştepta) până când mesajul va fi recepţionat de destinatar. Aceasta presupune că sursa este informată dacă destinatarul a preluat mesajul. Spunem că are loc o întâlnire (un rendez-vous), imaginându-ne că emiţătorul se întâlneşte cu destinatarul pentru a-i înmâna mesajul. Transmiterea unor informaţii prin fax poate fi încadrată în acest mod de sincronizare: emiţătorul primeşte imediat o confirmare din partea destinatarului că a primit mesajul.

3) Invocarea la distanţă (rendez-vous extins). În plus faţă de sincronizarea simplă, emiţătorul primeşte nu numai confirmarea de recepţie, dar şi un răspuns din partea destinatarului; abia după aceea emiţătorul poate transmite un alt mesaj.

În continuare ne vom rezuma la sincronizarea simplă.

Sincronizarea simplă are la bază lucrul cu canale. Afirmăm că:

În prezentarea clasică, un canal are o singură sursă şi o singură destinaţie, operaţiile fiind următoarele:- c ! e cu semnificaţia că expresia e este transmisă canalului c;- c ? v cu semnificaţia că variabila v primeşte valoarea asociată canalului c.

Pentru fiecare (nume de) canal, procesul sursă şi procesul destinaţie sunt unic determinate: sursa este unicul proces ce conţine operaţia c ! e, iar destinaţia este unicul proces în care apare operaţia complementară c ? v.

Să observăm însă că procesul sursă nu cunoaşte identitatea procesului destinaţie şi nici procesul destinaţie nu ştie de la ce proces primeşte mesajul. Legătura este considerată unidirecţională. Un canal transmite date, dar nu poate memora date.

Cele două operaţii sunt sincronizate, în sensul că transmisia are loc efectiv numai dacă procesul emiţător este gata să transmită, iar procesul de destinaţie este gata să recepţioneze; în caz contrar, procesul care ajunge primul la "întâlnire" (rendez-vous) este blocat în aşteptarea celuilalt. Nici un proces nu poate avansa înainte de terminarea transmiterii.

Precizăm că expresia transmisă prin canal şi variabila care o recepţionează trebuie să aibă acelaşi tip.

1

Utilizarea canalelor reprezintă modalitatea cea mai naturală pentru rezolvarea problemei sincronizării.

Page 2: c3_canale

Facilităţi Java pentru lucrul cu canale

Pachetul java.util.concurrent pune la dispoziţie clasa:

public class SynchronousQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, Serializable

Cozile sincrone sunt similare canalelor de tip rendez-vous din CSP şi Ada, cu excepţia faptului că mai multe fire pot încerca să pună în, respectiv să ia din, coadă . Orice operaţie de inserare trebuie să aştepte operaţia corespunzătoare de extragere efectuată de alt fir şi viceversa. O coada sincronă nu are capacitate, ceea ce exclude multe operaţii asupra cozilor. Nu este permisă inserarea în coadă a lui null.

Clasa permite o politică de fairness, stabilită prin constructor.Constructorii clasei sunt următorii:

public SynchronousQueue()

crează o coadă sincronă fără politică de fairness.public SynchronousQueue(boolean fair)

crează o coadă sincronă pentru care vom alege o politică de fairness punând true ca argument; altfel, ordinea de deblocare a firelor nu corespunde neapărat ordinii de blocare.

Metode care conduc la blocare până când cerinţele lor pot fi satisfăcute :void put(E e) throws InterruptedException, ClassCastException, NullPointerException, IllegalArgumentException

introduce elementul e în coadă, aşteptând - dacă este necesar - ca alt fir să-l primească.E take() throws InterruptedException

detectează şi întoarce (prin ştergere) capul cozii, aşteptând - dacă este necesar - ca alt fir să-l insereze.

Metode care întorc o valoare atunci când nu pot fi satisfăcute imediat :boolean offer(E e) throws ClassCastException, NullPointerException, IllegalArgumentException

adaugă în coadă, dacă este imediat posibil, elementul e. Întoarce true dacă adăugarea a avut loc.

E poll()întoarce capul cozii, ştergându-l din coadă; dacă la momentul invocării coada este vidă, întoarce null. Este folosită atunci când încercăm să citim din unul sau mai multe canale, cu alternativă (eventual vidă) în caz de eşec.

Conducte de canale

Exemplul 1. Generarea primelor n numere prime folosind canale.

2

2

cnci-1F0c0 Fi

ci Fn+1......

Pentru încercările de a pune/lua cu aşteptarea operaţiei complementare sunt disponibile metodele put şi take.

Pentru încercări de a pune/lua imediat sunt disponibile metodele offer şi poll.

Page 3: c3_canale

Vom lucra cu următoarele fire de executare (de tipul Filtru) şi canale (de tipul SynchronousQueue<Integer>):

- firul F0 pompează prin canalul c0 numerele naturale 2,3,... către firul F1;- fiecare dintre firele Fi (i=1,...,n) primeşte valori prin canalul ci-1 (notat în program prin

st) şi trimite valori lui Fi+1 prin canalul ci (notat în program prin dr);- firul Fn+1 primeşte o valoare prin canalul cn, tipăreşte mesajul "STOP" şi se termină.

Fiecare dintre firele Fi (i=1,...,n) acţionează astfel:- memorează prima valoare primită, numită valoarea sa de bază;- "filtrează" următoarele numere pe care le primeşte, în sensul că transmite la dreapta pe

conductă numărul primit din stânga doar dacă acesta nu este divizibil cu valoarea sa de bază.Valoarea de bază a fiecărui fir Fi (i=1,...,n) este un număr prim şi anume al i-lea număr

prim, deoarece este cel mai mic număr natural nedivizibil cu vreun număr prim mai mic decât el.Pentru terminarea programului, declarăm firele Fi (i=0,...,n) ca fiind fire daemon,

astfel încât terminarea firului principal şi a firului Fn+1 determină terminarea întregului program.

import java.util.*; import java.util.concurrent.*;

class Filtru extends Thread { static int n; int id; SynchronousQueue<Integer> st,dr; Filtru(int i, SynchronousQueue<Integer> s, SynchronousQueue<Integer> d) { id = i; st = s; dr = d; }

public void run() { int k=2; int x,y; try { if (id==0) while(true) dr.put(k++); else if(id==n+1) { st.take(); System.out.println("STOP"); } else { x = st.take(); System.out.print(x + "\t"); while(true) { y = st.take(); if(y%x != 0) dr.put(y); } } } catch(InterruptedException e) { } }}

class Prime { public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.print("n = "); int n = sc.nextInt(); Filtru.n = n; Filtru[] F = new Filtru[n+2]; SynchronousQueue<Integer> st=null, dr = new SynchronousQueue<Integer>(); for(int i=0; i<n+2; i++) { F[i] = new Filtru(i,st,dr); st = dr; dr = new SynchronousQueue<Integer>(); }

3

Page 4: c3_canale

for(int i=0; i<n+1; i++) F[i].setDaemon(true); for(int i=0; i<n+2; i++) F[i].start(); }}

Exemplul 2. Calcul de sume folosind conducte.

Într-un fişier apare o secvenţă de numere întregi. Se cere să calculăm suma lor folosind un număr de n fire care dorim să fie "tot timpul" active. Pentru simplificare, se consideră că lungimea secvenţei este multiplu de n.

Vom calcula sume de câte n numere consecutive din secvenţă, folosind o conductă de canale.

Filtrul F0 primeşte succesiv primul termen al sumelor prin canalul sus.Fiecare fir/filtru Fi (0<i<n) primeşte succesiv prin canalul sus al i-lea termen al

sumelor, iar prin canalul st primeşte suma termenilor precedenţi; după însumarea acestor două valori, trimite rezultatul lui Fi+1 prin canalul dr.

Filtru Fn-1 primeşte succesiv prin canalul sus ultimul termen al sumelor, iar prin canalul st primeşte suma termenilor precedenţi; după însumarea acestor două valori, afişează rezultatul şi incrementează câmpul static suma al clasei Filtru de tipul căreia sunt firele.

Valoarea n este citită de la intrarea standard, iar grupele de n termeni sunt citite dintr-un fişier a.b.

Oprirea programului este realizată prin declararea firelor Fi ca fire daemon.

import java.util.concurrent.*; import java.util.*; import java.io.*;

class Filtru extends Thread { static int n,suma=0; int x; int id; SynchronousQueue<Integer> sus,st,dr; Filtru(int i, SynchronousQueue<Integer> sus, SynchronousQueue<Integer> s, SynchronousQueue<Integer> d) { id = i; this.sus = sus; st = s; dr = d; }

public void run() { while(true) { try { if (id==0) dr.put(sus.take()); else if(id==n-1) { x = sus.take() + st.take(); suma += x; System.out.print(x + "\t"); } else dr.put(sus.take()+st.take()); } catch(InterruptedException e) { } } }}

4

4

sussussus

ststF0dr Fi

dr Fn-1......

Page 5: c3_canale

class SumeCanale { public static void main(String[] args) throws Exception { System.out.print("n = "); int n = new Scanner(System.in).nextInt(); Scanner in = new Scanner(new FileInputStream("a.b")); Filtru.n = n; Filtru[] F = new Filtru[n]; ArrayList<SynchronousQueue<Integer>> sus = new ArrayList<SynchronousQueue<Integer>>(); for(int i=0; i<n; i++) sus.add(new SynchronousQueue<Integer>());

SynchronousQueue<Integer> st=null, dr = new SynchronousQueue<Integer>(); for(int i=0; i<n; i++) { F[i] = new Filtru(i,sus.get(i),st,dr); st = dr; dr = new SynchronousQueue<Integer>(); } for(int i=0; i<n; i++) F[i].setDaemon(true); for(int i=0; i<n; i++) F[i].start();

while(in.hasNextInt()) for(int i=0; i<n; i++) sus.get(i).put(in.nextInt()); Thread.sleep(10); // se astepta afisarea ultimei sume partiale System.out.println("\nSuma = " + Filtru.suma); }}

Observaţie. Diagrama ocupării firelor are forma unui paralelogram cu laturile paralele orizontale, "înclinat" spre dreapta (liniile paralele orizontale corespund firelor):

Un mod tipic de lucru cu canale

Un mod tipic de lucru (folosit în cele ce urmează) este ca firele "efective" să transmită comenzi prin intermediul aceluiaşi canal unui fir supervizor, care în mod repetat: primeşte comenzi, identifică firul "emiţător" şi execută comanda respectivă.

În plus, de obicei supervizorul este singurul care deţine controlul asupra resurselor comune, excluderea reciprocă fiind astfel automată realizată.

5

Fir

t

canal

Fir[i]

Control

Page 6: c3_canale

Exemplul 3. Problema filozofilor chinezi.

Fiecăruia dintre cei n filozofi îi este asociat un proces F[i]. Aceste procese, împreună cu procesul Control ce asigură o desfăşurare corectă a operaţiilor, sunt executate concurent.

Fiecare fir F[i] trimite în ordine prin canal mai întâi perechea (i,'M') şi apoi perechea (i,'G') pentru a iniţia cele două operaţii (de a mânca şi de a gândi). Cele două operaţii sunt validate de firul Control.

Evidenţa beţişoarelor disponibile pentru fiecare filozof este asigurată de tabloul b; fiecare dintre componentele b[i] poate lua numai una dintre valorile 0,1,2, reprezentând numărul de beţişoare pe care le poate ridica filozoful F[i].

Nu este necesară o excludere reciprocă asupra beţişoarelor, deoarece ele sunt folosite numai în firul Control.

Dacă firul Control primeşte, prin canalul canal, o "comandă" (i,'M'), îi va da curs doar dacă b[i]=2 :

- în caz afirmativ, va actualiza numărul de beţişoare disponibile pentru filozoful din stânga şi pentru filozoful din dreapta sa şi va marca (prin valoarea booleană mananca[i]) faptul că acum filozoful F[i] mănâncă.

- în caz contrar, încercarea de a mânca este pierdută; cum ea este urmată de comanda (i,'G'), trebuie ca aceasta să nu determine vreo acţiune.

Dacă firul Control primeşte o "comandă" (i,'G'), îi va da curs numai dacă mananca[i] are valoarea true. În caz afirmativ, va actualiza numărul de beţişoare disponibile pentru filozoful din stânga şi pentru filozoful din dreapta sa şi va marca (prin valoarea booleană mananca[i]) faptul că acum filozoful F[i] nu mai mănâncă.

Vom declara firul control şi firele F[i] ca fire daemon. Drept urmare, terminarea firului principal (realizată prin citirea unui şir de caractere) va antrena terminarea întregului program.

Am ales ca firele corespunzătoare filozofilor să fie create în constructorul clasei Control.

import java.util.*; import java.util.concurrent.*;

class Fil_Canale { public static void main(String[] sss) throws Exception { Control control = new Control(); control.setDaemon(true); control.start(); System.in.read(); }}

class C { // Cerere int i; char c; C(int ii, char cc) { i = ii; c = cc; }}

6

6

canal

F[i]

Control

Page 7: c3_canale

class Filozof extends Thread {

int id; SynchronousQueue<C> canal; Filozof(int i, SynchronousQueue<C> c) { id = i; canal = c; }

static void delay(int i) { try { Thread.sleep( (int) (i*Math.random()) ); } catch(InterruptedException e) { } }

public void run() { try { for(int i=0; i<10; i++) { canal.put(new C(id,'M')); delay(100); canal.put(new C(id,'G')); delay(100); } } catch(InterruptedException e) { } }}

class Control extends Thread { int n,i; SynchronousQueue<C> canal = new SynchronousQueue<C>(); int[] b; boolean[] mananca; Filozof[] F; Scanner sc = new Scanner(System.in);

Control() { System.out.print("n = "); n = sc.nextInt(); b = new int[n]; mananca = new boolean[n]; for(int i=0; i<n; i++) { mananca[i] = false; b[i] = 2; } F = new Filozof[n]; for(i=0; i<n; i++) F[i] = new Filozof(i,canal); for(i=0; i<n; i++) F[i].setDaemon(true); for(i=0; i<n; i++) F[i].start(); }

public void run() { int st,dr,i; try { while(true) { C Ob = canal.take(); st = (Ob.i==0 ? n-1 : Ob.i-1); dr = (Ob.i+1) % n; if(Ob.c == 'M') if( (b[Ob.i] == 2) ) { b[st]--; b[dr]--; mananca[Ob.i] = true; System.out.print(Ob.i + "M" + "\t"); } else System.out.print("~" + Ob.i + "M" + "\t"); //ratare else if(mananca[Ob.i]) { b[st]++; b[dr]++; System.out.print(Ob.i + "G" + "\t"); mananca[Ob.i] = false; }

7

Page 8: c3_canale

} } catch(InterruptedException e) { } }}

Exemplul 4. Problema Cititori - Scriitori.

Vom folosi aceeaşi abordare ca în problema filozofilor. Celor nc cititori şi celor ns scriitori le este asociat câte un fir. Aceste fire sunt executate concurent cu firul Control care gestionează toate operaţiile asupra cărţii. Toţi cititorii şi scriitorii trimit prin unicul canal canal firului Control perechi de cereri pentru deschiderea şi închiderea cărţii.

Firul Control primeşte pe rând prin canal câte o cerere şi, dacă este posibilă rezolvarea ei, o duce la îndeplinire. În particular vor exista cereri de deschidere irosite/eşuate/pierdute, fapt de care trebuie ţinut cont în primul rând la cererea de închidere corespunzătoare.

Acţiunile cititorilor, scriitorilor şi procesului de control se execută concurent. Fiecare acţiune a unui cititor sau scriitor constă în a transmite intenţia sa de a deschide cartea şi apoi de a o închide.

Fiecare cerere este o pereche (i,s) unde i este numărul de ordine la cititorului sau scriitorului, iar s este unul dintre următoarele şiruri de caractere:

- "cdesch" pentru deschidere pentru citire;- "cinch" pentru închidere pentru citire;- "sdesch" pentru deschidere pentru scriere;- "sinch" pentru închidere pentru citire.

Procesul Control mai ţine evidenţa cererilor de deschidere/închidere prin intermediul tablourilor citeste şi scrie de dimensiuni nc respectiv ns, unde de exemplu citeste[i] este true dacă şi numai dacă cititorul i a deschis cu succes cartea, dar nu a închis-o încă. Aceasta este necesar deoarece cererile de deschidere care nu pot fi satisfăcute sunt considerate eşuate (nu sunt reîncercate) şi deci o cerere de închidere trebuie validată doar dacă cititorul/scriitorul a deschis cu succes cartea.

Variabila nrcit ţine evidenţa numărului de cititori activi, iar variabila booleană sactiva va avea valoarea true dacă şi numai dacă un scriitor are acces la carte.

import java.util.*; import java.util.concurrent.*;

class B { static SynchronousQueue<C> canal = new SynchronousQueue<C>(true); static void delay(int i) { try { Thread.sleep( (int) (i*Math.random()) ); } catch(InterruptedException e) { } }}

class C { // Cerere int i; String s; C(int ii, String ss) { i = ii; s = ss; }

8

8

canal

C[i]

Control

S[i]

Page 9: c3_canale

}

class Cititor extends Thread { int id; Cititor(int i) { id = i; }

public void run() { try { for(int i=0; i<10; i++) { B.canal.put(new C(id,"cdesch")); B.delay(50); B.canal.put(new C(id,"cinch")); B.delay(50); } } catch(InterruptedException e) { } }} class Scriitor extends Thread { int id; Scriitor(int i) { id = i; }

public void run() { try { for(int i=0; i<10; i++) { B.canal.put(new C(id,"sdesch")); B.delay(10); B.canal.put(new C(id,"sinch")); B.delay(10); } } catch(InterruptedException e) { } }}

class Control extends Thread { boolean sactiva; int nrcit, nc, ns; boolean[] citeste, scrie;

Control(int c, int s) { sactiva = false; nrcit = 0; nc = c; ns = s; citeste = new boolean[nc]; scrie = new boolean[ns]; for(int i=0; i<nc; i++) citeste[i] = false; for(int i=0; i<ns; i++) scrie[i] = false; }

public void run() { try { while(true) { C Ob = B.canal.take(); if(Ob.s.equals("cdesch")) if( !sactiva ) { System.out.print("C" + Ob.i + "(\t"); nrcit++; citeste[Ob.i] = true; } else System.out.print("~C" + Ob.i + "(\t"); else if(Ob.s.equals("cinch")) { if( citeste[Ob.i] ) { System.out.print("C" + Ob.i + ")\t"); nrcit--; citeste[Ob.i] = false; }

9

Page 10: c3_canale

else; } else if(Ob.s.equals("sdesch")) { if( !sactiva && (nrcit==0) ) { System.out.print("S" + Ob.i + "(\t"); sactiva = true; scrie[Ob.i] = true; } else System.out.print("~S" + Ob.i + "(\t"); } else if(Ob.s.equals("sinch") && scrie[Ob.i] ) { System.out.print("S" + Ob.i + ")\t"); sactiva = false; scrie[Ob.i] = false; } else; } } catch(InterruptedException e) { } }}

class CitScr_Canale { public static void main(String[] sss) throws Exception { int nc = 8, ns = 5, i; Cititor[] C = new Cititor[nc]; for(i=0; i<nc; i++) C[i] = new Cititor(i); Scriitor[] S = new Scriitor[ns]; for(i=0; i<ns; i++) S[i] = new Scriitor(i); Control control = new Control(nc,ns); for(i=0; i<nc; i++) C[i].start(); for(i=0; i<ns; i++) S[i].start(); control.setDaemon(true); control.start(); }}

Situaţiile de nedeterminare sunt următoarele: - nicio persoană nu are acces la carte: orice cerere de deschidere pentru citire sau pentru scriere poate fi satisfăcută; - cel puţin un cititor este activ: oricare dintre cititorii activi poate înceta consultarea cărţii şi oricare alt cititor poate începe consultarea cărţii.

Clasa Exchanger

Tot în pachetul java.util.concurrent apare clasa:public class Exchanger<tip> extends Object

Un obiect de tipul Exchanger reprezintă un punct de sincronizare la care două fire se pot racorda pentru a schimba între ele elemente de tipul tip. Fiecare fir oferă un obiect ca argument la invocarea metodei exchange; când ambele fire ajung la "întâlnire", are loc interschimbarea obiectelor oferite.

Un astfel de obiect poate fi privit ca o formă bidirecţională a lui SynchronousQueue. Aplicaţiile se regăsesc în algoritmii genetici şi în proiectarea conductelor (canalelor).

Observaţie (privind consistenţa memoriei). Pentru fiecare pereche de fire ce schimbă între ele cu succes obiecte, acţiunile premergătoare lui exchange() au loc înaintea celor ce urmează unui return din invocarea exchange() corespunzătoare din celălalt fir.

10

10

Page 11: c3_canale

Constructorul are forma:Exchanger< tip > ()

Metoda:public tip exchange(tip x) throws InterruptedExceptionaşteaptă un alt fir să ajungă la punctul de schimb (afară de cazul când firul curent este în starea "întrerupt") şi apoi are loc transferul bidirecţional. Dacă un alt fir aşteaptă deja la punctul de schimb, executarea sa este reluată şi are loc schimbul. Dacă niciun fir nu aşteaptă, firul curent devine "adormit" până când fie alt fir ajunge la punctul de schimb, fie alt fir întrerupe firul curent.

În cadrul schimbului, valoarea trimisă este x, iar valoarea primită este cea returnată de metodă.

Dacă firul curent este în starea "întrerupt" când s-a ajuns la exchange() sau este întrerupt în timp ce aşteaptă schimbul, este lansată excepţia InterruptedException şi firul iese din starea "întrerupt".

Menţionăm că metoda are şi o variantă ce prevede o aşteptare limitată în timp.

Exemplul 5. Revenim la problema celor două tablouri s şi t care în final trebuie să conţină cele mai mici, respectiv cele mai mari valori din totalitatea elementelor lor.

Soluţia anterioară, cea care folosea semafoare, presupunea că ambele procese au acces la ambele tablouri. În soluţia care urmează, care foloseşte clasa Exchange, fiecare fir cunoaşte doar propriul tablou.

Cele două fire se aşteaptă până când îşi calculează maximul, respectiv minimul elementelor din tabloul propriu, după care îşi interschimbă cele două valori. Firul corespunzător lui s se termină când primeşte o valoare mai mică decât maximul elementelor proprii; firul corespunzător lui t se termină când primeşte o valoare mai mare decât minimul elementelor proprii.import java.util.*; import java.util.concurrent.*;

class MinMaxEx { public static void main(String[] www) throws Exception { Exchanger schimb = new Exchanger<Integer>(); S FirS = new S(schimb); T FirT = new T(schimb); FirS.start(); FirT.start(); FirS.join(); FirT.join(); FirS.scrie(); System.out.println(); FirT.scrie(); }}

class S extends Thread { int[] s; int ns; Exchanger schimb; S(Exchanger e) { schimb = e; Scanner sc = new Scanner(System.in); System.out.print("ns = "); ns = sc.nextInt(); s = new int[ns]; System.out.print("Dati cele ns=" + ns +" elemente: "); for(int i=0; i<ns; i++) s[i] = sc.nextInt(); }

public void run() { try { Integer max = 0, min_t; int poz = -1; while(true) {

11

Page 12: c3_canale

max = Integer.MIN_VALUE; for(int i=0; i<ns; i++) if(s[i]>max) { max = s[i]; poz = i; } min_t = (Integer) schimb.exchange(max); System.out.println("S : am primit " + min_t); if(min_t>max) break; else s[poz] = min_t; } } catch(Exception e) { } }

void scrie() { for(int i=0; i<ns; i++) System.out.print("\t" + s[i]); }}

class T extends Thread { int[] t; int nt; Exchanger schimb; T(Exchanger e) { schimb = e; Scanner sc = new Scanner(System.in); System.out.print("nt = "); nt = sc.nextInt(); t = new int[nt]; System.out.print("Dati cele nt=" + nt +" elemente: "); for(int i=0; i<nt; i++) t[i] = sc.nextInt(); } public void run() { try { Integer min = 0, max_s; int poz = -1; while(true) { min = Integer.MAX_VALUE; for(int i=0; i<nt; i++) if(t[i]<min) { min = t[i]; poz = i; } max_s = (Integer) schimb.exchange(min); System.out.println("T : am primit " + max_s); if(max_s<min) break; else t[poz] = max_s; } } catch(Exception e) { } }

void scrie() { for(int i=0; i<nt; i++) System.out.print("\t" + t[i]); }}

12

12