Documente Academic
Documente Profesional
Documente Cultură
01CompendiuJava PDF
01CompendiuJava PDF
COMPENDIU JAVA
} // Salut
Mai întâi câteva precizări, care reprezintă de fapt şi primele reguli Java.
Un program Java constă din definirea şi instanţierea unei clase. Numele textului
sursă este format din numele clasei urmat de sufixul .java. Unele programe
pot conţine în acelaşi text sursă mai multe clase, dar atunci numai una dintre ele
va fi vizibilă în exterior, celelalte fiind numai de uz intern.
17
Un program standalone trebuie să conţină metoda statică main. Ea are
ca parametru un tablou având ca şi elemente şiruri de caractere. Fiecare element
al tabloului conţine un argument transmis în linia de comandă ce lansează
programul în execuţie.
Tipărirea este executată de către metoda println a obiectului out din
clasa System.
Compilatorul Java poartă numele de javac.
Lansarea compilării programului se face cu comanda:
javac Salut.java
import java.applet.*;
import java.awt.*;
18
Scrierea textului se face prin intermediul metodei paint. Aceasta
realizează rescrierea metodei paint din java.awt.Component, având
rolul de a "picta" un obiect grafic g. Concret, este vorba de scrierea unui string
începând de la pozitia (x= 10 pixeli spre dreapta, y = 50 pixeli în jos).
Compilarea sursei se face folosind aceeaşi comandă de compilare:
javac SalutAp.java
19
În fig. 1.1 este prezentat rezultatul execuţiei folosind navigatorul
Mozilla.
Dacă se doreşte utilizarea interpretorului standard Java appletviewer, se
va lansa comanda:
appletviewer SalutAp.html
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
20
public void init() {
} // PrimCompus.init
} // PrimCompus
<html>
<title>Fereastra applet </title>
<Applet code="PrimCompus.class" width=250 height=350> </Applet>
</html>
21
Pentru ca programul să poată fi rulat, cu (aproape) acelaşi efect, în cadrul
metodei main trebuie executate câteva acţiuni, de altfel specifice interfeţelor
grafice de tip standalone.
Mai întâi am definit şi construit obiectul oPrimCompus de tipul clasei.
Apoi am definit un obiect oFrame de tip Frame (tipul clasic de
fereastră din pachetul awt). Am fixat titlul acestei ferestre şi dimensiunile ei (în
pixeli).
Am construit obiectul l, de tip WindowListener, definit printr-o clasă
internă, anonimă, de tip WindowsAdapter. Rolul acestui obiect este de a
intercepta evenimentul de apăsare a butonului de distrugere a ferestrei şi de a
termina apoi programul. (Ca rezultat al compilării, din această clasă anonimă se
va crea fişierul PrimCompus$1.class)
Am adăugat, în poziţia centrală a obiectului oFrame obiectul
oPrimCompus şi am cerut ferestrei să devină vizibilă.
În sfârşit, am lansat metoda init a obiectului oPrimCompus, după
care comportarea va fi ca şi la applet: va căuta metoda paint şi va afişa string-
ul în fereastră. Rezultatul execuţiei va fi cel din fig. 1.4.
22
thread-urilor (firelor de execuţie) care încă nu s-au terminat şi apoi execuţia se
termină.
În momentul lansării în execuţie, parametrii transmişi prin linia de
comandă sunt depuşi în vectorul de string-uri notat mai sus prin argv. De aici
utilizatorul le poate accesa. De exemplu, programul 1.6 afişează toate aceste
argumente (tabloul de string-uri din linia de comandă l-am notat cu a).
23
cu fiecare nouă distribuţie Java. De exemplu, lucrările [44, 4] prezintă aceste
elemente. Noi vom prezenta deosebirile şi asemănările în ipoteza că cititorul
cunoaşte, la un nivel minimal, limbajele C++ şi (eventual) Java.
Java nu acceptă mecanismul typedef, nici noţiunea de preprocesor. În
consecinţă, construcţiile #define, #if #include ş.a. nu sunt admise.
Dispare deci noţiunea de fişier header. De asemenea, Java nu suportă tipul de
date enum. În locul lui lui define, în Java se pot declara constante cu ajutorul
cuvântului cheie final. Aceste constante pot fi folosite ca înlocuitori pentru
tipul enum.
Ca şi C++, Java operează cu tipul class, dar nu suportă nici tipul
struct, nici union.
Programele standalone C++ cer o funcţie numită main. Pe lângă
aceasta, în programele C++ pot să apară numeroase alte funcţii, atât
independente, cât şi funcţii membre ale claselor (metode). În Java, funcţiile sunt
numai membre ale claselor (metode), deci nu există funcţii independente,
neaparţinând claselor. De asemenea, Java nu admite funcţii globale şi date
globale.
În Java, toate clasele sunt, în ultimă instanţă, descendente din clasa
Object. În schimb, în C++ este posibilă crearea unor arbori de clase fără nici
o legătură între ei.
Toate definiţiile de funcţii (metode) în Java sunt conţinute în interiorul
claselor cărora le aparţin. În C++ este permisă definirea lor în afara claselor. De
asemenea, în C++ este permisă definirea explicită de funcţii inline, în timp
ce în Java această opţiune este lăsată pe seama interpretorului JVM.
Atât C++ cât şi Java permit definirea de variabile şi metode clasă
(static), invocabile independent de existenţa sau nu a unor instanţieri ale
clasei respective.
Java permite utilizarea conceptului interface. Prin intermediul ei se
pot defini prototipuri de metode şi eventual declararea de constante. Într-o
interfaţă nu sunt permise definirea de metode, nici declararea de variabile
membru. C++ nu suportă conceptul de interfaţă. Atât Java, cât şi C++ admit
clase abstracte. Vom reveni asupra acestui concept.
Java nu permite moştenirea multiplă. Într-o oarecare măsură, utilizarea
interfeţelor suplineşte în Java această absenţă. Moştenirea simplă este,
conceptual la fel în Java ca şi în C++, însă în partea de implementare diferenţele
sunt mari.
Java nu acceptă instrucţiunea goto, deşi goto este cuvânt rezervat (dar
nefolosit)! În schimb, Java permite break şi continue, atât în forma
existentă în C++, cât şi extinderile break şi continue etichetate. Acestea
din urmă nu sunt suportate de C++. În forma simplă ele comandă părăsirea,
respectiv reiterarea celui mai interior corp al unui ciclu. Cele etichetate
acţionează în acelaşi mod, dar asupra ciclurilor (mai exterioare) marcate printr-o
24
etichetă. Aceste extinderi pentru break şi continue permit înlocuirea, în
toate cazurile de structuri de control fireşti, a instrucţiunii goto [16].
Java nu permite suprapunerea operatorilor. De asemenea, nu permite
conversia implicită (automată) de tip.
Spre deosebire de C++, Java are obiecte de tip String, obiecte
nemodificabile. Obiectele Java de tip StringBuffer permit şi modificări,
precum şi conversii facile între cele două tipuri de string-uri.
Tablourile în Java sunt obiecte. Într-un tablou Java, elementele lui sunt
indexate de la 0 la length-1. length este o variabilă a unui obiect tablou,
indicând lungimea maximă a domeniului de indexare. Elementele unui tablou
sunt (evident) toate de acelaşi tip, care poate fi orice, inclusiv un alt tablou.
Astfel se implementează tablourile multidimensionale. Java permite operaţia de
atribuire la nivel de tablou. Dimensiunea unui tablou se fixează dinamic, nu
static ca în C++. Spre deosebire de C++, în Java se controlează, la orice
invocare, limitele indicilor, evitându-se astfel indexarea în afara domeniului.
Java nu permite operarea cu pointeri, implicit nu există nici aritmetica
de pointeri. Automat, a dispărut şi echivalenţa dintre pointeri, nume de tablouri
etc. Natural, absenţa pointerilor implică absenţa operatorului de selecţie din
pointer "->".
Operatorul rezoluţie scope "::" din C++ nu este folosit în Java. Toate
selecţiile se fac cu operatorul ".".
Pentru expresiile condiţionale Java s-a introdus tipul boolean, inexistent
în C++. A dispărut astfel statutul de TRUE pentru o valoare nenulă şi FALSE
pentru o valoare nulă, aşa cum stau lucrurile în C.
În ceea ce priveşte tipurile de date primitive, ele sunt aproape identice
cu cele din C. Vom reveni cu detalii într-o secţiune ulterioară.
Operatorii sunt, practic cei din C. Operatorii pe biţi acţionează numai
asupra întregilor. Operatorul >> face deplasarea la dreapta cu propagarea bitului
de semn, iar un nou operator, >>>, face aceeaşi deplasare cu completarea de
zerouri pentru biţii eliberaţi. Singura suprapunere, implicită, este += ce poate
indica şi concatenarea de string-uri.
Cât despre spaţiile de nume: în Java orice variabilă (câmp) şi orice
metodă este declarată numai în interiorul unei clase şi face parte din structura
acesteia. De asemenea, fiecare clasă face parte dintr-un pachet. Pachetele Java
sunt astăzi cele care permit o mare dezvoltare şi extindere a platformelor Java.
O colecţie de clase înrudite, cu ţinta spre un anumit tip de prelucrări, constituie
în fapt o bibliotecă ce poate fi integrată în orice aplicaţie Java. Este suficient să
amintim aici că, dacă la Java 1.0 ([10]) erau opt pachete standard, la Java 1.2
erau 58 [44] şi numărul pachetelor, standard sau de aplicaţie este în continuă
creştere.
Accesul la nume se poate face:
25
• implicit - între clasele aceluiaşi pachet;
• declarat public - accesibil de oriunde;
• declarat protected - accesibil din clasă sau clasele subordonate;
• declarate private - accesibil numai în interiorul clasei.
În C++ orice nume (variabilă, constantă, nume de funcţie etc.) trebuie
mai întâi declarat sau definit şi abia apoi utilizat. În Java această restricţie nu
mai există.
Constructorii, atât în Java cât şi în C++ pot fi suprapuşi. În Java, toate
obiectele sunt transmise prin referinţă, eliminându-se astfel constructorii de
copiere din C++. În Java nu există destructori! Interpretorul JVM are un thread
specializat, care rulează în continuu (cu prioritatea cea mai mică), numit
garbage collector. Acesta face oficiile de destructor atunci când un obiect nu
mai este referit de nicăieri.
Garbage collection
Obiectele Java îşi ocupă memoria necesară în mod dinamic, în faza de
execuţie. Alocarea de memorie este făcută automat, deci lipsesc apelurile
similare lui malloc. De asemenea, nu se face eliberarea explicită a
spaţiului ocupat, deci nu există apeluri similare lui free din C şi C++.
Distrugerea obiectelor se face automat, printr-un mecanism clasic de
garbage collection [76, 17]. Eliberarea spaţiului ocupat de un obiect se
face, fără ştirea utilizatorului, după ce obiectul nu mai este vizibil şi nici
în viaţă.
26
1.2.2. Modificatorii static, final, syncronized şi native
Modificatorul static
Poate preceda un câmp (variabilă) sau o metodă a unei clase, pentru a
specifica faptul că elementul respectiv este propriu clasei respective şi că
el nu depinde de una sau alta dintre obiectele rezultate din instanţierile
clasei respective. Deci câmpul (variabila) are aceeaşi valoare indiferent
de instanţiere, iar metoda funcţionează independent de instanţiere. Un
element static poate fi accesat indiferent de faptul că există sau nu o
instanţiere a clasei respective.
De exemplu, variabila out din clasa System poate fi accesat chiar dacă
nu există o instanţiere a acestei clase, motiv pentru care putem invoca
metoda println a lui: System.out.println("…");
Modificatorul final
Ataşat unei variabile sau unei metode indică faptul că elementul
respectiv este în ultima lui formă. Din această cauză, nici variabila, nici
metoda finală nu mai pot fi suprascrise în cadrul subclaselor derivate. O
variabilă finală se comportă ca şi o constantă.
Modificatorul syncronized
Poate să caracterizeze o metodă (există şi instrucţiunea syncronized
asupra căreia vom reveni). Dacă două sau mai multe thread-uri invocă o
aceeaşi metodă sincronizată pentru un acelaşi obiect, atunci doar unul
dintre ele o execută, celelalte aşteaptă ca acesta să îşi termine invocarea
ei, după care un altul îşi porneşte exclusiv execuţia ş.a.m.d. Deci, dacă
două thread-uri solicită pentru acelaşi obiect a o metodă sincronizată
met, adică ambele invocă a.met, atunci ele vor fi executate unul
după celălalt. Dacă însă există două obiecte diferite a şi b, atunci două
thread-uri pot să execute simultan apelurile a.met() b.met().
Modificatorul native
Indică faptul că metoda respectivă este descrisă într-un fişier extern
provenit dintr-o altă compilare. De exemplu dintr-o compilare C. Evi-
dent, acest gen de metode nu mai păstrează independenţa de platformă.
27
1.2.3. Cuvinte cheie
Java utilizează următoarele cuvinte cheie:
abstract boolean break byte case
catch char class const continue
default do double else extends
final finally float for goto
if implements import instanceof int
interface long native new null
package private protected public return
static short super switch synchronized
this throw throws transient try
void volatile while
Tipul char
Se reprezintă pe doi octeţi în Unicode şi respectă convenţiile de
conversie la întregi din C.
String-urile
Se scriu ca şi în C (între ghilimele). Spre deosebire de C, în Java nu
există continuare (linie terminată cu \ în C sau C++) pentru string-urile
lungi. În schimb, operatorul + Java este extins şi pentru concatenarea de
28
string-uri. Deci şirurile lungi se scriu folosindu-se operatorul + de conca-
tenare. Există tipul de date String.
Tipul Boolean
Este nou introdus în Java (nu este în C). În schimb dispare convenţia C
cu 0 având valoarea false şi diferit de 0 având valoarea true. Un tip
Boolean nu este un întreg şi nici nu poate fi convertit la acesta.
Tipurile întregi
La tipurile întregi, împărţirea cu 0 activează excepţia numită
ArithmeticException. Constantele întregi lungi au după şirul de
cifre litera l sau L. Constantele octale încep cu 0, iar cele hexazecimale
cu 0x sau 0X.
Tipurile flotante
Pentru tipurile flotante există constantele predefinite:
POSITIVE_INFINITY, NEGATIVE_INFINITY, NaN (Not a Number).
29
1.2.5. Tipul referinţă obiect
Tipurile de date neprimitive sunt obiecte, în care includem şi tablouri.
Ele sunt numite în Java “tipuri referinţă”, deoarece sunt manevrate prin adresă:
compilatorul pune adresa unui obiect sau tablou într-o variabilă şi aşa este
transmis către metode. Prin contrast, tipurile primitive sunt manevrate “prin
valoare”.
Deoarece obiectele sunt transmise prin referinţă, este posibil ca două
variabile diferite să se refere la acelaşi obiect. De exemplu:
Button p, q;
p = new Button();
q = p; int i = 3;
p.setLabel(“Ok”); int j = i;
String s = q.getLabel(); i = 2;
30
Din raţiuni de portabilitate, de independenţă de platformă şi de siguranţă,
nu există pointeri! Deci, nu se pune problema referirii, dereferirii, conversiei
referinţelor la tablouri în întregi etc.
Constanta null indică inexistenţa unui obiect sau a unui tablou.
31
catch (TipExceptie1 objExceptie1) {
. . . // Tratare exceptie TipExceptie1
}
catch (TipExceptie2 objExceptie2) {
. . . // Tratare exceptie TipExceptie2
}
. . . ...
finally {
. . // Bloc de instructiuni ce se executa
// inainte de a se incheia blocul try
}
32
• FileNotFoundException
• ...
• RuntimeError
• ArithmeticException
• IndexOutOfBoundsException
• ArrayIndexOutOfBoundsException
• StringIndexOutOfBoundsException
• NullPointerException
• ...
• ...
• Error
• VirtualMachineError
• ...
33
Singura restricţie la constructorii multipli este aceea că orice doi cons-
tructori diferiţi trebuie să aibă liste de parametri la care cel puţin o pereche de
parametri corespunzători să difere. De exemplu, primul şi al doilea constructor
diferă prin parametrul al doilea, prezent doar la primul constructor; al doilea şi
al treilea constructor au numai câte un parametru dar aceştia sunt de tipuri
diferite ş.a.m.d.
Pentru a apela un constructor, în cadrul unui new numele clasei va avea
argumentele dorite, din care compilatorul va deduce despre care constructor este
vorba. De exemplu, următoarele linii au acelaşi efect:
Cerc c = new Cerc(); c.x = 1.0; c.r = 0.5;
Distrugerea obiectelor
Distrugerea obiectelor se face automat prin mecanismul de Garbage
Collection care este un thread (fir de execuţie) de prioritate minimă. El culege
zonele de memorie după ce acestea nu mai sunt folosite şi eliberează spaţiul
ocupat de ele. Aceasta este, după părerea noastră, cea mai mare diferenţă de stil
de programare între Java şi C sau C++. Urmează ca cititorii să se convingă
singuri: va părea ciudat, cel puţin la început, să nu gestionezi eliberarea spaţiilor
de memorie!
Este însă posibil ca eliberarea de spaţiu să se petreacă uneori târziu.
Uneori (poate) este utilă forţarea eliberării mai rapide. Iată o posibilă schemă:
34
public static void main() {
int T = new int[100000];
- - -
calcule(T);
T = null; // Din acest moment, tabloul poate fi distrus
- - -
restul calculelor
- - -
}
Finalizator de obiecte
De multe ori, anumite obiecte sau resurse, cum ar fi spre exemplu,
descriptorii de fişiere şi socket-urile, sunt păstrate indefinit. Pentru a forţa
eliberarea acestora, se pot folosi metode finalizatori, după modelul de mai jos.
protected void finalize() throws IOException
{ if (fd != null) close(); // fd este un descriptor }
35
două cercuri primite prin parametri. Evident, prima va fi dependentă de
instanţiere, dar a doua va fi independentă de instanţiere.
public class Cerc {
public double x, y, r;
- - -
public Cerc maxim (Cerc c)
{ if (c.r > r) return c; else return this;}
Iniţializatori statici
Atât variabilele instanţă, cât şi cele statice, pot fi iniţializate, prin
mecanismele cunoscute din C şi C++. De exemplu,
static int nr_cercuri = 0;
36
public class Cerc {
- - -
static private double[] sinus = new double[1000];
static private double[] cosinus = new double[1000];
- - -
static {
double x, deltax;
int i;
deltax = (Math.PI / 2) / (1000-1);
for (i=0, x=0.0; i<1000; i++, x+=deltax) {
sinus[i] = Math.sin(x);
cosinus[i] = Math.cos(x);
}
- - -
}
37
după care să definească toate metodele declarate de interfaţă. De asemenea, se
impune ca toate metodele interfeţelor să fie declarate public. În consecinţă,
clasele care implementează interfaţa trebuie să fie declarate public.
38
} // Distanta.main
} // Distanta
import java.io.*;
} // MediiPonderate
39
Figura 1.5. Utilizarea MediiPonderate
import java.io.*;
import java.util.*;
40
Math.abs(r.nextInt())%car.length())); // Genereaza un caracter
System.out.println(lin); // Tipareste o parola
} // for: cele 2000 parole generate
} // GenPasswd.main
} // GenPasswd
import java.io.*;
import java.util.*;
41
Iată, în fig. 1.6, cum arată rezultatul execuţiei acestui program.
42
for(int j=i+1; j<n; j++) {
d=(String)ts.elementAt(j);
s=(String)ts.elementAt(i);
if (s.compareTo(d) > 0) {
ts.set(i, d);
ts.set(j, s);
} // if
} // for j
} // for i
System.out.println("\nLiniile ordonate lexicografic:\n");
for (int i=0; i<n;i++){
s=(String)ts.elementAt(i);
System.out.println(s); // Afiseaza liniile
} // for
} // Linii.main
} // Linii
43
sincronizarea se bazează pe conceptul de monitor. Este cunoscut faptul că
operarea multithreading dă multă bătaie de cap programatorilor C şi C++.
Programatorii trebuie să-şi implementeze mecanisme proprii de blocare a unor
secţiuni critice şi de partajare a unor resurse critice. Primitivele furnizate de
Java reduc esenţial acest efort.
Metodele wait()
Pun obiectul în aşteptare până la apariţia unui eveniment. Ultimele două
forme indică o durată maximă de aşteptare, fie numai în
milisecunde, fie în milisecunde plus nanosecunde.
Metoda getClass()
Întoarce clasa din care s-a instanţiat obiectul apelant al metodei.
Metoda equals()
Oferă, prin suprascriere în subclase, posibilitatea de a se defini ce
înseamnă egalitatea a două obiecte din clasa respectivă. Implementarea
acestei metode trebuie să respecte, pentru orice referiri nenule x, y,
z, următoarele condiţii:
• reflexivitate, adică x.equals(x) trebuie să întoarcă true;
44
• simetrie, adică x.equals(y) este true dacă şi numai dacă
y.equals(x) este true;
• tranzitivitate, adică x.equals(y) este true şi y.equals(z) este
true implică x.equals(z) este true;
• consistenţă, adică datele utilizate în metoda equals să nu modifice
relaţia între două obiecte; deci, pentru două obiecte x şi y, apelurile
multiple x.equals(y) trebuie să întoarcă mereu fie true, fie
false;
• referinţa null: x.equals(null) să fie mereu false.
Metoda clone()
Creează o copie a obiectului. Înţelesul exact al “copiei” depinde de clasă.
În general următoarele condiţii sunt adevărate:
• x.clone() != x este true;
• x.clone90.getClass() == x.getClass() este true;
• x.clone().equals(x) este true.
Sunt extrem de rare cazurile în care apar mici abateri de la aceste reguli.
Este vorba de situaţiile în care suprascrierea lui clone realizează o
“copiere superficială”.
Metoda finalize()
Poate fi suprascrisă în subclase şi ea conţine acţiunile pe care compo-
nenta “garbage collection” din JVM le va executa imediat înainte de a
distruge un obiect. Trebuie remarcat faptul că programul utilizator nu
poate stabili cu exactitate momentul şi ordinea de execuţie a metodelor
finalize legate de obiecte expirate.
Metoda toString()
Întoarce reprezentarea textuală a obiectului. Este utilă uneori în activi-
tatea de depanare. Se recomandă suprascrierea ei dacă se intenţionează a
fi utilizată.
45
atunci corpul este ataşat thread-ului nou creat. Prin constructor, sau mai târziu
folosind metoda setName(), se poate atribui thread-ului un nume. Dacă nu se
specifică altă valoare, atunci numele implicit va fi “Thread-“+n, unde n este
un întreg. Metoda getName() întoarce numele curent al thread-ului.
Metoda start()
Lansează în execuţie noul thread. Din acest moment, execuţia progra-
mului este controlată de (cel puţin) două thread-uri: pe de o parte
funcţionează thread-ul curent - cel care execută start(), iar pe de altă
parte se lansează în execuţie noul thread, ale cărui instrucţiuni sunt
precizate în metoda run().
Metoda run
Conţine corpul efectiv al thread-ului. Întreaga activitate curentă a noului
thread trebuie descrisă prin suprascrierea acestei metode.
Metodele sleep()
Pun thread-ul curent în aşteptare un anumit interval de timp.
Metodele join()
Aşteaptă ca obiectul thread care le apelează să moară. Ultimele două
forme limitează timpul maxim de aşteptare.
Metoda yield()
Cedează controlul de la obiectul thread la planificatorul JVM, pentru a
permite unui alt thread (green) să ruleze.
Metoda interrupt()
Trimite o întrerupere obiectului thread care o invocă.
Metoda isAlive()
Răspunde dacă obiectul thread apelant este în viaţă sau nu.
46
Metoda activeCount()
Întoarce numărul de thread-uri active din grupul curent. Metoda
currentThread() întoarce numărul thread-ului curent. Metoda
enumerate() întoarce în tabloul t thread-urile membre ale grupului
curent şi ale celor din subgrupuri. Metoda getThreadGroup()
întoarce grupul curent de thread-uri. Metoda dumpStack() afişează la
ieşirea standard stiva curentă de thread-uri.
Crearea unui thread folosind Java API se poate face în două moduri:
• implementând o clasă derivată din clasa predefinită Thread, clasă
care face parte din pachetul java.lang şi ca urmare este importată
automat în orice program Java;
• definind o clasă care implementează interfaţa Runnable.
Primul dintre ele constă în a declara o clasă ca subclasă a clasei
Thread. În interiorul acesteia se redefineşte metoda run(), metodă care
conţine “corpul” de instrucţiuni ale thread-ului. După aceea, se poate defini şi
instanţia noua subclasă. Schematic, crearea se face conform modelului de mai
jos.
class ThreadPropriu extend Thread {
// - - - datele subclasei - - -
ThreadPropriu ( /* parametrii constructorului */ ) {
// - - - descrierea constructorului
}
public void run() {
// - - - defineste corpul thread-ului ThreadPropriu - - -
}
47
}
48
stop() din partea altui thread, fie prin trimiterea unei întreruperi
(interrupt()) din partea altui thread.
• S-a invocat metoda stop() a obiectului thread (metodă depăşită).
La terminarea unui thread, resursele specifice acestuia sunt eliberate.
Obiectul thread nu va fi şters din memorie de către garbage collector decât la
dereferenţierea manuală a instanţei (atribuirea valorii null pentru referinţă)
sau la terminarea programului.
49
Schematic, aplicarea declaraţiilor synchronized se face după una din
următoarele două scheme:
public - - - synchronized - - - numeMetoda (… ){ - - - }
sau
synchronized (obiect) { bloc (secvenţă de instrucţiuni) }
50
Reluarea exclusivităţii cedate de către un thread care a executat un wait
are loc atunci când thread-ul respectiv “se trezeşte” ca urmare a receptării unui
anunţ trimis de către un alt thread activ care execută una dintre metodele
notify() sau notifyAll(). În urma acestui eveniment, thread-ul îşi
recapătă exclusivitatea cedată anterior şi îşi continuă execuţia instrucţiunilor de
unde şi-a oprit-o anterior (după apelul wait()).
Anunţul efectuat prin notify() se adresează tuturor thread-urilor care
sunt în starea Waiting, dar numai unul dintre ele îşi va relua activitatea, celelalte
vor rămâne în continuare în starea Waiting. În schimb, anunţul efectuat prin
notifyAll() este recepţionat de către toate thread-urile aflate în starea
Waiting. În consecinţă toate thread-urile ies din această stare şi îşi continuă
activitatea.
Este posibil ca un thread să solicite intrarea în starea Waiting doar pentru
un interval limitat de timp. În acest caz el va specifica în apelul wait durata
maximă pe care o admite.
De asemenea, un thread poate ieşi din starea Waiting dacă un alt thread
apelează metoda interrupt() a thread-ului aflat în starea Waiting. În acest
caz execuţia lui se reia cu secvenţa catch ce interceptează întreruperea.
În sumar, prezentăm scenariul de comunicare între thread-uri, folosind
funcţiile wait şi notify:
1. Un thread are nevoie în timpul execuţiei de o condiţie despre care
presupune că va fi realizată în alt thread.
2. Primul thread aşteaptă realizarea condiţiei cu ajutorul metodei
wait(), oferind astfel fiecărui thread suport pentru acest meca-
nism de comunicare.
3. Thread-ul care realizează condiţia în timpul execuţiei, anunţă acest
lucru unui thread care aşteaptă, prin apelul funcţiei notify().
Metodele wait() şi notify() trebuie apelate din interiorul unei
metode sau bloc sincronizat, pentru a preveni eventuale rezultate incorecte,
generate de lipsa sincronizării. Înainte de a intra în aşteptare folosind wait(),
thread-ul curent eliberează obiectul de sincronizare blocat şi-l obţine din nou
când thread-ul este anunţat că a fost realizată condiţia aşteptată.
Metodele wait() şi notify() sunt metode native, scrise în C şi nu
este posibilă reimplementarea lor folosind numai limbajul Java.
Mai facem câteva precizări în legătură cu comportarea mecanismului
wait/notify:
• Dacă notify() este apelată dintr-un thread, dar nu este nici un
thread care să aştepte, atunci metoda notify() returnează fără nici
o eroare.
51
• Funcţia wait() eliberează obiectul de sincronizare la intrarea în
aşteptare şi îl reobţine la recepţionarea unei notificări. Sistemul
asigură atomicitatea acestor operaţii critice.
• Dacă există mai multe thread-uri care aşteaptă o anumită condiţie, nu
se poate ştii care thread primeşte notificarea după apelul funcţiei
notify(). Acest lucru depinde de JVM şi de mecansimul de
planificare a thread-urilor.
• Dacă se doreşte apelul funcţiilor wait() şi notify() din metode
statice, atunci se declară în interiorul clasei un obiect de sincronizare
static, iar funcţiile wait() şi notify() se vor apela pentru acest
obiect static.
Diferenţa esenţială dintre funcţiile wait şi sleep constă în faptul că
metoda sleep nu este obligatoriu să fie apelată dintr-un bloc sincronizat şi nu
implică blocarea sau deblocarea unui obiect de sincronizare.
52
Partea esenţială pentru sincronizare constă din definirea variabilei
cititori şi a metodelor citeste şi scrie.
Variabila clasă cititori reţine de fiecare dată câţi cititori sunt activi
la un moment dat. După cum se poate observa, instanţa curentă a clasei Bd este
blocată (pusă în regim de monitor) pe parcursul acţiunilor asupra variabilei
cititori. Aceste acţiuni sunt efectuate numai în interiorul metodelor scrie
şi citeste.
Metoda citeste incrementează (în regim monitor) numărul de cititori.
Apoi, posibil concurent cu alţi cititori, îşi efectuează activitatea, care aici constă
doar în afişarea stării curente. La terminarea acestei activităţi, în regim monitor
decrementează şi anunţă thread-urile aşteptătoare. Acestea din urmă sunt, cu
siguranţă, numai scriitori.
Metoda scrie este declarată synchronized (regim monitor), deoa-
rece întreaga ei activitate se desfăşoară fără ca celelalte procese să acţioneze
asupra Bd.
53
1.6.4.1. Clasa RecipientCuvinte
import java.util.*;
class RecipientCuvinte {
private Vector b = new Vector();
private String continut;
private int RECIPIENT;
54
e.printStackTrace();
} // catch
} // while
b.add(continut);
notify();
} // RecipientCuvinte.put
} // RecipientCuvinte
import java.util.*;
import java.io.*;
55
try {
s = in.readLine();
} // try
catch (Exception e) {
e.printStackTrace();
} // catch
if (s == null)
break; // Sfarsit fisier intrare
if (s.equals("")) {
r.put("\t"); // Depune marcaj de paragraf
continue;
} // if
if (s.charAt(0) == '\t')
r.put("\t"); // Depune marcaj de paragraf
st = new StringTokenizer(s.trim());
for ( ; st.hasMoreTokens(); )
r.put(st.nextToken()); // Depune cuvant
} // for
r.put("\n"); // Depune sfarsit de fisier
} // ProducatorCuvinte.run
} // ProducatorCuvinte
56
Extragerile din recipient se fac prin invocarea metodei get a recipien-
tului. Este posibil (şi probabil) ca din când în când thread-ul să aştepte extra-
gerea din recipient înainte de a depune.
Sursa clasei este prezentată în programul 1.14.
import java.io.*;
import java.util.*;
57
s += " "+separator+st.nextToken();
nrSeparatoriMaiLungi--;
} // if
return s;
} // ConsumatorCuvinte.distantare
} // ConsumatorCuvinte
58
import java.io.*;
class AliniereCuvinte {
final public int LUNGIME = 60, RECIPIENT = 100;
RecipientCuvinte r;
ProducatorCuvinte p;
ConsumatorCuvinte c;
} // AliniereCuvinte.main
} // AliniereCuvinte
59
Metoda exec lansează un thread separat pentru execuţia comenzii
(comenzilor) respective şi returnează un obiect de tip Process, asociat
procesului creat, astfel, în sistem. Dacă aplicaţia lansată din programul Java are
nevoie de date de intrare, acestea sunt transmise aplicaţiei externe cu ajutorul
obiectului OutputStream, ataşat obiectului Process:
OutputStream getOutputStream();
import java.io.*;
60
System.out.println(e2);}
}// ExecJava.main
}// ExecJava
61