Capitolul IX
Fire de execuție și sincronizare

O aplicație Java rulează în interiorul unui proces al sistemului de operare. Acest proces constă din segmente de cod și segmente de date mapate într-un spațiu virtual de adresare. Fiecare proces deține un număr de resurse alocate de către sistemul de operare, cum ar fi fișiere deschise, regiuni de memorie alocate dinamic, sau fire de execuție. Toate aceste resurse deținute de către un proces sunt eliberate la terminarea procesului de către sistemul de operare.

Un fir de execuție este unitatea de execuție a unui proces. Fiecare fir de execuție are asociate o secvență de instrucțiuni, un set de regișitri CPU și o stivă. Atenție, un proces nu execută nici un fel de instrucțiuni. El este de fapt un spațiu de adresare comun pentru unul sau mai multe fire de execuție. Execuția instrucțiunilor cade în responsabilitatea firelor de execuție. În cele ce urmează vom prescurta uneori denumirea firelor de execuție, numindu-le pur și simplu fire .

În cazul aplicațiilor Java interpretate, procesul deține în principal codul interpretorului iar codul binar Java este tratat ca o zonă de date de către interpretor. Dar, chiar și în această situație, o aplicație Java poate avea mai multe fire de execuție, create de către interpretor și care execută, seturi distincte de instrucțiuni binare Java.

Fiecare dintre aceste fire de execuție poate rula în paralel pe un procesor separat dacă mașina pe care rulează aplicația este o mașină cu mai multe procesoare. Pe mașinile monoprocesor, senzația de execuție în paralel a firelor de execuție este creată prin rotirea acestora pe rând la controlul unității centrale, câte o cuantă de timp fiecare. Algoritmul de rotire al firelor de execuție este de tip round-robin.

Mediul de execuție Java execută propriul său control asupra firelor de execuție. Algoritmul pentru planificarea firelor de execuție, prioritățile și stările în care se pot afla acestea sunt specifice aplicațiilor Java și implementate identic pe toate platformele pe care a fost portat mediul de execuție Java. Totuși, acest mediu știe să profite de resursele sistemului pe care lucrează. Dacă sistemul gazdă lucrează cu mai multe procesoare, Java va folosi toate aceste procesoare pentru a-și planifica firele de execuție. Dacă sistemul oferă multitasking preemptiv, multitaskingul Java va fi de asemenea preemptiv, etc.

În cazul mașinilor multiprocesor, mediul de execuție Java și sistemul de operare sunt responsabile cu repartizarea firelor de execuție pe un procesor sau altul. Pentru programator, acest mecanism este complet transparent, neexistând nici o diferență între scrierea unei aplicații cu mai multe fire pentru o mașină cu un singur procesor sau cu mai multe. Desigur, există însă diferențe în cazul scrierii aplicațiilor pe mai multe fire de execuție față de acelea cu un singur fir de execuție, diferențe care provin în principal din cauza necesității de sincronizare între firele de execuție aparținând aceluiași proces.

Sincronizarea firelor de execuție înseamnă că acestea se așteaptă unul pe celălalt pentru completarea anumitor operații care nu se pot executa în paralel sau care trebuie executate într-o anumită ordine. Java oferă și în acest caz mecanismele sale proprii de sincronizare, extrem de ușor de utilizat și înglobate în chiar sintaxa de bază a limbajului.

La lansarea în execuție a unei aplicații Java este creat automat și un prim fir de execuție, numit firul principal. Acesta poate ulterior să creeze alte fire de execuție care la rândul lor pot crea alte fire, și așa mai departe. Firele de execuție dintr-o aplicație Java pot fi grupate în grupuri pentru a fi manipulate în comun.

În afară de firele normale de execuție, Java oferă și fire de execuție cu prioritate mică care lucrează în fundalul aplicației atunci când nici un alt fir de execuție nu poate fi rulat. Aceste fire de fundal se numesc demoni și execută operații costisitoare în timp și independente de celelalte fire de execuție. De exemplu, în Java colectorul de gunoaie lucrează pe un fir de execuție separat, cu proprietăți de demon. În același fel poate fi gândit un fir de execuție care execută operații de încărcare a unor imagini din rețea.

O aplicație Java se termină atunci când se termină toate firele de execuție din interiorul ei sau când nu mai există decât fire demon. Terminarea firului principal de execuție nu duce la terminarea automată a aplicației. ^

9.1 Crearea firelor de execuție

Există două căi de definire de noi fire de execuție: derivarea din clasa Thread a noi clase și implementarea într-o clasă a interfeței Runnable .

În primul caz, noua clasă moștenește toate metodele și variabilele clasei Thread care implementează în mod standard, în Java, funcționalitatea de lucru cu fire de execuție. Singurul lucru pe care trebuie să-l facă noua clasă este să reimplementeze metoda run care este apelată automat de către mediul de execuție la lansarea unui nou fir. În plus, noua clasă ar putea avea nevoie să implementeze un constructor care să permită atribuirea unei denumiri firului de execuție.

Dacă firul are un nume, acesta poate fi obținut cu metoda getName care returnează un obiect de tip String .

Iată un exemplu de definire a unui nou tip de fir de execuție:

class FirNou extends Thread {
public FirNou( String nume ) {
// apelează constructorul din Thread
super( nume ); 
}
public void run() {
while( true ) { // fără sfârșit
System.out.println( getName() +
" Tastati ^C" );
}
}
}

Dacă vom crea un nou obiect de tip FirNou și îl lansăm în execuție acesta va afișa la infinit mesajul "Tastați ^C". Întreruperea execuției se poate face într-adevăr prin tastarea caracterului ^C, caz în care întreaga aplicație este terminată. Atâta timp însă cât noul obiect nu va fi întrerupt din exterior, aplicația va continua să se execute pentru că mai există încă fire de execuție active și indiferent de faptul că firul de execuție principal s-a terminat sau nu.

Iată și un exemplu de aplicație care folosește această clasă:

public TestFirNou {
public static void main( String[] ) {
new FirNou( "Primul" ).start();
}
}

Metoda start , predefinită în obiectul Thread lansează execuția propriu-zisă a firului. Desigur există și căi de a opri execuția la nesfârșit a firului creat fie prin apelul metodei stop , prezentată mai jos, fie prin rescrierea funcției run în așa fel încât execuția sa să se termine după un interval finit de timp.

A doua cale de definiție a unui fir de execuție este implementarea interfeței Runnable într-o anumită clasă de obiecte. Această cale este cea care trebuie aleasă atunci când clasa pe care o creăm nu se poate deriva din clasa Thread pentru că este important să fie derivată din altă clasă. Desigur, moștenirea multiplă ar rezolva această problemă, dar Java nu are moștenire multiplă.

Această nouă cale se poate folosi în modul următor:

class Oclasa {
…
}
class FirNou extends Oclasa implements Runnable {
public void run() {
for( int i = 0; i < 100; i++ ) {
System.out.println( "pasul " + i );
}
}
…
}
public class TestFirNou {
public static void main( String argumente[] ) {
new Thread( new FirNou() ).start();
// Obiectele sunt create și folosite imediat
// La terminarea instrucțiunii, ele sunt automat
// eliberate nefiind referite de nimic
}
}

După cum observați, clasa Thread are și un constructor care primește ca argument o instanță a unei clase care implementează interfața Runnable . În acest caz, la lansarea în execuție a noului fir, cu metoda start , se apelează metoda run din acest obiect și nu din instanța a clasei Thread .

Atunci când dorim să creăm un aplet care să ruleze pe un fir de execuție separat față de pagina de navigator în care rulează pentru a putea executa operații în fereastra apletului și în același timp să putem folosi în continuare navigatorul, suntem obligați să alegem cea de-a doua cale de implementare. Aceasta pentru că apletul nostru trebuie să fie derivat din clasa standard Applet . Singura alternativă care ne rămâne este aceea de a implementa în aplet interfața Runnable . ^

9.2 Stările unui fir de execuție

Un fir de execuție se poate afla în Java în mai multe stări, în funcție de ce se întâmplă cu el la un moment dat.

Atunci când este creat, dar înainte de apelul metodei start, firul se găsește într-o stare pe care o vom numi Fir Nou Creat . În această stare, singurele metode care se pot apela pentru firul de execuție sunt metodele start și stop . Metoda start lansează firul în execuție prin apelul metodei run . Metoda stop omoară firul de execuție încă înainte de a fi lansat. Orice altă metodă apelată în această stare provoacă terminarea firului de execuție prin generarea unei excepții de tip IllegalThreadStateException .

Dacă apelăm metoda start pentru un Fir Nou Creat firul de execuție va trece în starea Rulează . În această stare, instrucțiunile din corpul metodei run se execută una după alta. Execuția poate fi oprită temporar prin apelul metodei sleep care primește ca argument un număr de milisecunde care reprezintă intervalul de timp în care firul trebuie să fie oprit. După trecerea intervalului, firul de execuție va porni din nou.

În timpul în care se scurge intervalul specificat de sleep , obiectul nu poate fi repornit prin metode obișnuite. Singura cale de a ieși din această stare este aceea de a apela metoda interrupt . Această metodă aruncă o excepție de tip InterruptedException care nu este prinsă de sleep dar care trebuie prinsă obligatoriu de metoda care a apelat metoda sleep . De aceea, modul standard în care se apelează metoda sleep este următorul:

…
try {
sleep( 1000 ); // o secundă
} catch( InterruptedException ) {
…
}
…

Dacă dorim oprirea firului de execuție pe timp nedefinit, putem apela metoda suspend . Aceasta trece firul de execuție într-o nouă stare, numită Nu Rulează . Aceeași stare este folosită și pentru oprirea temporară cu sleep . În cazul apelului suspend însă, execuția nu va putea fi reluată decât printr-un apel al metodei resume . După acest apel, firul va intra din nou în starea Rulează .

Pe timpul în care firul de execuție se găsește în starea Nu Rulează , acesta nu este planificat niciodată la controlul unității centrale, aceasta fiind cedată celorlalte fire de execuție din aplicație.

Firul de execuție poate intra în starea Nu Rulează și din alte motive. De exemplu se poate întâmpla ca firul să aștepte pentru terminarea unei operații de intrare/ieșire de lungă durată caz în care firul va intra din nou în starea Rulează doar după terminarea operației.

O altă cale de a ajunge în starea Nu Rulează este aceea de a apela o metodă sau o secvență de instrucțiuni sincronizată după un obiect. În acest caz, dacă obiectul este deja blocat, firul de execuție va fi oprit până în clipa în care obiectul cu pricina apelează metoda notify sau notifyAll .

În fine, atunci când metoda run și-a terminat execuția, obiectul intră în starea Mort . Această stare este păstrată până în clipa în care obiectul

este eliminat din memorie de mecanismul de colectare a gunoaielor. O altă posibilitate de a intra în starea Mort este aceea de a apela metoda stop .

Atunci când se apelează metoda stop , aceasta aruncă cu o instrucțiune throw o eroare numită ThreadDeath . Aceasta poate fi prinsă de către cod pentru a efectua curățenia necesară. Codul necesar este următorul:

…
try {
firDeExecutie.start();
…
} catch( ThreadDeath td ) {
… // curățenie
throw td; // se aruncă obiectul mai departe
// pentru a servi la distrugerea
// firului de execuție
}

Desigur, firul de execuție poate fi terminat și pe alte căi, caz în care metoda stop nu este apelată și eroarea ThreadDeath nu este aruncată. În aceste situații este preferabil să ne folosim de o clauză finally ca în:

…
try {
firDeExecutie.start();
…
} finally {
..// curățenie
}

În fine, dacă nu se mai poate face nimic pentru că firul de execuție nu mai răspunde la comenzi, puteți apela la calea disperată a metodei destroy . Din păcate, metoda destroy termină firul de execuție fără a proceda la curățirile necesare în memorie.

Atunci când un fir de execuție este oprit cu comanda stop , mai este nevoie de un timp până când sistemul efectuează toate operațiile necesare opririi. Din această cauză, este preferabil să așteptăm în mod explicit terminarea firului prin apelul metodei join :

firDeExecutie.stop()
try {
firDeExecutie.join();
} catch( InterruptedException e ) {
…
}

Excepția de întrerupere trebuie prinsă obligatoriu. Dacă nu apelăm metoda join pentru a aștepta terminarea și metoda stop este de exemplu apelată pe ultima linie a funcției main , există șansa ca sistemul să creadă că firul auxiliar de execuție este încă în viață și aplicația Java să nu se mai termine rămânând într-o stare de așteptare. O puteți desigur termina tastând ^C. ^

9.3 Prioritatea firelor de execuție

Fiecare fir de execuție are o prioritate cuprinsă între valorile MIN_PRIORITY și MAX_PRIORITY. Aceste două variabile finale sunt declarate în clasa Thread . În mod normal însă, un fir de execuție are prioritatea NORM_PRIORITY, de asemenea definită în clasa Thread .

Mediul de execuție Java planifică firele de execuție la controlul unității centrale în funcție de prioritatea lor. Dacă există mai multe fire cu prioritate maximă, acestea sunt planificate după un algoritm round-robin. Firele de prioritate mai mică intră în calcul doar atunci când toate firele de prioritate mare sunt în starea Nu Rulează .

Prioritatea unui fir de execuție se poate interoga cu metoda getPriority care întoarce un număr întreg care reprezintă prioritatea curentă a firului de execuție. Pentru a seta prioritatea, se folosește metoda setPriority care primește ca parametru un număr întreg care reprezintă prioritatea dorită.

Schimbarea priorității unui fir de execuție este o treabă periculoasă dacă metoda cu prioritate mare nu se termină foarte repede sau dacă nu are opriri dese. În caz contrar, celelalte metode nu vor mai putea primi controlul unității centrale.

Există însă situații în care putem schimba această prioritate fără pericol, de exemplu când avem un fir de execuție care nu face altceva decât să citească caractere de la utilizator și să le memoreze într-o zonă temporară. În acest caz, firul de execuție este în cea mai mare parte a timpului în starea Nu Rulează din cauză că așteaptă terminarea unei operații de intrare/ieșire. În clipa în care utilizatorul tastează un caracter, firul va ieși din starea de așteptare și va fi primul planificat la execuție din cauza priorității sale ridicate. În acest fel utilizatorul are senzația că aplicația răspunde foarte repede la comenzile sale.

În alte situații, avem de executat o sarcină cu prioritate mică. În aceste cazuri, putem seta pentru firul de execuție care execută aceste sarcini o prioritate redusă.

Alternativ, putem defini firul respectiv de execuție ca un demon. Dezavantajul în această situație este faptul că aplicația va fi terminată atunci când există doar demoni în lucru și există posibilitatea pierderii de date. Pentru a declara un fir de execuție ca demon, putem apela metoda setDaemon. Această metodă primește ca parametru o valoare booleană care dacă este true firul este făcut demon și dacă nu este adus înapoi la starea normală. Putem testa faptul că un fir de execuție este demon sau nu cu metoda isDemon^

9.4 Grupuri de fire de execuție

Uneori avem nevoie să acționăm asupra mai multor fire de execuție deodată, pentru a le suspenda, reporni sau modifica prioritatea în bloc. Din acest motiv, este util să putem grupa firele de execuție pe grupuri. Această funcționalitate este oferită în Java de către o clasă numită ThreadGroup .

La pornirea unei aplicații Java, se creează automat un prim grup de fire de execuție, numit grupul principal, main . Firul principal de execuție

face parte din acest grup. În continuare, ori de câte ori creăm un nou fir de execuție, acesta va face parte din același grup de fire de execuție ca și firul de execuție din interiorul căruia a fost creat, în afară de cazurile în care în constructorul firului specificăm explicit altceva.

Într-un grup de fire de execuție putem defini nu numai fire dar și alte grupuri de execuție. Se creează astfel o arborescență a cărei rădăcină este grupul principal de fire de execuție.

Pentru a specifica pentru un fir un nou grup de fire de execuție, putem apela constructorii obișnuiți dar introducând un prim parametru suplimentar de tip ThreadGroup . De exemplu, putem folosi următorul cod:

ThreadGroup tg = new ThreadGroup( "Noul grup" );
Thread t = new Thread( tg, "Firul de executie" );

Acest nou fir de execuție va face parte dintr-un alt grup de fire decât firul principal. Putem afla grupul de fire de execuție din care face parte un anumit fir apelând metoda getThreadGroup , ca în secvența:

Thread t = new Thread( "Firul de Executie" );
ThreadGroup tg = t.getThreadGroup();

Operațiile definite pentru un grup de fire de execuție sunt clasificabile în operații care acționează la nivelul grupului, cum ar fi aflarea numelui, setarea unei priorități maxime, etc., și operații care acționează asupra fiecărui fir de execuție din grup, cum ar fi stop , suspend sau resume . Unele dintre aceste operații necesită aprobarea controloarelor de securitate acest lucru făcându-se printr-o metodă numită checkAccess . De exemplu, nu puteți seta prioritatea unui fir de execuție decât dacă aveți drepturile de acces necesare. ^

9.5 Enumerarea firelor de execuție

Pentru a enumera firele de execuție active la un moment dat, putem folosi metoda enumerate definită în clasa Thread precum și în clasa ThreadGroup . Această metodă primește ca parametru o referință către un tablou de referințe la obiecte de tip Thread pe care îl umple cu referințe către fiecare fir activ în grupul specificat.

Pentru a afla câte fire active sunt în grupul respectiv la un moment dat, putem apela metoda activeCount din clasa ThreadGroup . De exemplu:

public listeazaFire {
ThreadGroup grup = Thread.currentThread().getThreadGroup();
int numarFire = grup.activeCount();
Thread fire[] = new Thread[numarFire];
grup.enumerate( fire );
for( int i = 0; i < numar; i++ ) {
System.out.println( fire[i].toString() );
}
}

Metoda enumerate întoarce numărul de fire memorate în tablou, care este identic cu numărul de fire active. ^

9.6 Sincronizare

În unele situații se poate întâmpla ca mai multe fire de execuție să vrea să acceseze aceeași variabilă. În astfel de situații, se pot produce încurcături dacă în timpul unuia dintre accese un alt fir de execuție modifică valoarea variabilei.

Limbajul Java oferă în mod nativ suport pentru protejarea acestor variabile. Suportul este construit de fapt cu granulație mai mare decât o singură variabilă, protecția făcându-se la nivelul obiectelor. Putem defini metode, în cadrul claselor, care sunt sincronizate.

Pe o instanță de clasă, la un moment dat, poate lucra o singură metodă sincronizată. Dacă un alt fir de execuție încearcă să apeleze aceeași metodă pe aceeași instanță sau o altă metodă a clasei de asemenea declarată sincronizată, acest al doilea apel va trebui să aștepte înainte de execuție eliberarea instanței de către cealaltă metodă.

În afară de sincronizarea metodelor, se pot sincroniza și doar blocuri de instrucțiuni. Aceste sincronizări se fac tot în legătură cu o anumită instanță a unei clase. Aceste blocuri de instrucțiuni sincronizate se pot executa doar când instanța este liberă. Se poate întâmpla ca cele două tipuri de sincronizări să se amestece, în sensul că obiectul poate fi blocat de un bloc de instrucțiuni și toate metodele sincronizate să aștepte, sau invers.

Declararea unui bloc de instrucțiuni sincronizate se face prin:

synchronize ( Instanță ) {

Instrucțiuni

}

iar declararea unei metode sincronizate se face prin folosirea modificatorului synchronize la implementarea metodei. ^

9.7 Un exemplu

Exemplul următor implementează soluția următoarei probleme: Într-o țară foarte îndepărtată trăiau trei înțelepți filozofi. Acești trei înțelepți își pierdeau o mare parte din energie certându-se între ei pentru a afla care este cel mai înțelept. Pentru a tranșa problema o dată pentru totdeauna, cei trei înțelepți au pornit la drum către un al patrulea înțelept pe care cu toții îl recunoșteau că ar fi mai bun decât ei.

Când au ajuns la acesta, cei trei i-au cerut să le spună care dintre ei este cel mai înțelept. Acesta, a scos cinci pălării, trei negre și două albe, și li le-a arătat explicându-le că îi va lega la ochi și le va pune în cap câte o pălărie, cele două rămase ascunzându-le. După aceea, le va dezlega ochii, și fiecare dintre ei va vedea culoarea pălăriei celorlalți dar nu și-o va putea vedea pe a sa. Cel care își va da primul seama ce culoare are propria pălărie, acela va fi cel mai înțelept.

După explicație, înțeleptul i-a legat la ochi, le-a pus la fiecare câte o pălărie neagră și le-a ascuns pe celelalte două. Problema este aceea de a descoperi care a fost raționamentul celui care a ghicit primul că pălăria lui este neagră.

Programul următor rezolvă problema dată în felul următor: Fiecare înțelept privește pălăriile celorlalți doi. Dacă ambele sunt albe, problema este rezolvată, a lui nu poate fi decât neagră. Dacă vede o pălărie albă și una neagră, atunci el va trebui să aștepte puțin să vadă ce spune cel cu pălăria neagră. Dacă acesta nu găsește soluția, înseamnă că el nu vede două pălării albe, altfel ar fi găsit imediat răspunsul. După un scurt timp de așteptare, înțeleptul poate să fie sigur că pălăria lui este neagră.

În fine, dacă ambele pălării pe care le vede sunt negre, va trebui să aștepte un timp ceva mai lung pentru a vedea dacă unul dintre concurenții săi nu ghicește pălăria. Dacă după scurgerea timpului nici unul nu spune nimic, înseamnă că nici unul nu vede o pălărie albă și una neagră. Înseamnă că propria pălărie este neagră.

Desigur, raționamentul pleacă de la ideea că ne putem baza pe faptul că toți înțelepții gândesc și pot rezolva probleme ușoare. Cel care câștigă a gândit doar un pic mai repede. Putem simula viteza de gândiri cu un interval aleator de așteptare până la luarea deciziilor. În realitate, intervalul nu este aleator ci dictat de viteza de gândire a fiecărui înțelept.

Cei trei înțelepți sunt implementați identic sub formă de fire de execuție. Nu câștigă la fiecare rulare același din cauza caracterului aleator al implementării. Înțeleptul cel mare este firul de execuție principal care controlează activitatea celorlalte fire și le servește cu date, culoarea pălăriilor, doar în măsura în care aceste date trebuie să fie accesibile. Adică nu se poate cere propria culoare de pălărie.

Culoarea inițială a pălăriilor se poate rescrie din linia de comandă.

import java.awt.Color;
// clasa Filozof implementează comportamentul
// unui concurent
class Filozof extends Thread {
// părerea concurentului despre culoarea
// pălăriei sale. Null dacă încă nu și-a
// format o părere.
Color parere = null;
Filozof( String nume ) {
super( nume );
}
public void run() {
// concurentii firului curent
Filozof concurenti[] = new Filozof[2];
// temporar
Thread fire[] = new Thread[10];
int numarFire = enumerate( fire );
for( int i = 0, j = 0; i < numarFire &&   j < 2; i++ ) {
if( fire[i] instanceof Filozof && 
fire[i] != this ) {
concurenti[j++] = (Filozof)fire[i];
}
}
while( true ) {
Color primaCuloare = Concurs.culoare( this, 
concurenti[0] );
Color adouaCuloare =
 Concurs.culoare( this, concurenti[1] );
if( primaCuloare == Color.white &&
adouaCuloare == Color.white ) {
synchronized( this ) {
parere = Color.black;
}
} else if( primaCuloare == Color.white ){
try{
sleep( 500 );
} catch( InterruptedException e ){
};
if( Concurs.culoare( this, concurenti[1]) != concurenti[1].aGhicit()) {
synchronized( this ) {
parere = Color.black;
};
}
} else if( adouaCuloare == Color.white ) {
try{
 sleep( (int)( Math.random()*500));
} catch( InterruptedException e ) {
};
if( Concurs.culoare(this, concurenti[0] ) != concurenti[0].aGhicit()) {
synchronized( this ) {
parere = Color.black;
};
}
} else {
try {
sleep( (int)( Math.random()*500)+500 );
 } catch( InterruptedException e ) {
};
if( Concurs.culoare(this, concurenti[0]) != concurenti[0].aGhicit() &&
 Concurs.culoare( this, 
concurenti[1] ) !=
concurenti[1].aGhicit() ) {
synchronized( this ) {
parere = Color.black;
};
}
}
}
}
public synchronized Color aGhicit() {
return parere;
}
}
public class Concurs {
private static Color palarii[] = {
Color.black, Color.black, Color.black
 };
private static Filozof filozofi[] = new Filozof[3];
public static void main( String args[] ) {
for( int i = 0; i < args.length && i <   3; i++ ) {
if( args[i].equalsIgnoreCase( "alb" ) ) {
palarii[i] = Color.white;
} else if(args[i].equalsIgnoreCase("negru")) {
palarii[i] = Color.black;
}
}
for( int i = 0; i < 3; i++ ) {
filozofi[i] = new Filozof( "Filozoful " + 
( i + 1 ) );
}
for( int i = 0; i < 3; i++ ) {
filozofi[i].start();
}
System.out.println( "Concurenti:" );
for( int i = 0; i < 3; i++ ) {
System.out.println( "\t" +
filozofi[i].getName() + " "
+ (( palarii[i] == Color.white ) ?
"alb":"negru" ) );
}
gata:
while( true ) {
for( int i = 0; i < 3; i++ ) {
if( filozofi[i].aGhicit()==palarii[i] ) {
System.out.println( 
filozofi[i].getName() +
" a ghicit." );
break gata;
}
}
}
for( int i = 0; i < 3; i++ ) {
filozofi[i].stop();
try {
filozofi[i].join();
} catch( InterruptedException e ) {};
}
}
public static Color culoare( Filozof filozof,
Filozof concurent ) {
if( filozof != concurent ) {
for( int i = 0; i < 3; i++ ) {
if( filozofi[i] == concurent ) {
return palarii[i];
}
}
}
return null;
}
} ^

9.8 Un exemplu Runnable

Exemplul următor implementează problema celor 8 dame, și anume: găsește toate posibilitățile de a așeza pe o tablă de șah 8 regine în așa fel încât acestea să nu se bată între ele. Reginele se bat pe linie, coloană sau în diagonală.

Soluția de față extinde problema la o tablă de NxN căsuțe și la N regine. Parametrul N este citit din tagul HTML asociat apletului.

import java.awt.*;
import java.applet.Applet;
public
class QueensRunner extends Applet implements Runnable {
 int n;
 int regine[];
 int linie;
 Image queenImage;
 Thread myThread;
 public void start() {
if( myThread == null ) {
myThread = new Thread( this, "Queens" );
myThread.start();
}
 }
 public void stop() {
myThread.stop();
myThread = null;
 }
 public void run() {
while( myThread != null ) {
 nextSolution();
 repaint();
 try {
 myThread.sleep( 1000 );
 } catch ( InterruptedException e ){
 }
 }
 }
 boolean isGood() {
 for( int i = 0; i < linie; i++ ) {
 if( regine[linie] == regine[i] ||
 Math.abs( regine[i] - 
regine[linie] ) == Math.abs( i - linie ) ) {
 return false;
 }
 }
 return true;
 }
 
 void nextSolution() {
 while( true ) {
 if( linie < 0 ) {
 linie = 0;
 }
 regine[linie]++;
 if( regine[linie] > n ) {
 regine[linie] = 0;
 linie--;
 } else {
 if( isGood() ) {
 linie++;
 if( linie >= n ) {
 break;
 }
 }
 }
 } 
 }
 
 public void init() {
 String param = getParameter( "Dimension" );
 if( param == null ) {
 n = 4;
 } else {
 try {
 n = Integer.parseInt( param );
 } catch( NumberFormatException e ) {
 n = 4;
 }
 if( n < 4 ) {
 n = 4;
 }
 }
 regine = new int[n + 1];
 for( int i = 0; i < n; i++ ) {
 regine[i] = 0;
 }
 linie = 0;
 queenImage = getImage(getCodeBase(), "queen.gif"   );
 }
 public void paint( Graphics g ) {
 Dimension d = size();
 g.setColor( Color.red );
 int xoff = d.width / n;
 int yoff = d.height / n;
 for( int i = 1; i < n; i++ ) {
 g.drawLine( xoff * i, 0, xoff * i, d.height );
 g.drawLine( 0, yoff * i, d.width, yoff * i );
 }
 for( int i = 0; i < n; i++ ) {
 for( int j = 0; j < n; j++ ) {
 if( regine[i] - 1 == j ) {
 g.drawImage(queenImage, 
i*xoff + 1, j*yoff + 1, this);
 }
 }
 }
 }
 public String getAppletInfo() {
 return "Queens by E. Rotariu";
 }
} ^
[cuprins]
(C) IntegraSoft 1996-1998