Cuvântul cheie synchronized
Cuvântul rezervat synchronized are rolul de a defini blocuri de cod și metode ce reprezintă secțiuni/regiuni critice.
public class MyConcurrentArray<T> {
private static int numberOfInstances = 0;
private T[] content;
public MyConcurrentArray(int size) {
if (size > 0) {
content = new T[size];
} else {
throw new RuntimeException("Negative size provided for MyConcurrentArray instantiation.");
}
synchronized(MyConcurrentArray.class) {
++numberOfInstances;
}
}
//Metodă sincronizată.
public synchronized T get(int index) {
if (index < content.length) {
return content[index];
}
throw new IndexOutOfBoundsException(index + " is out of bounds for MyConcurrentArray of size " + content.length);
}
public void set(int index, T newT) {
//Bloc de cod sincronizat ce folosește instanța curentă (this) pe post de zăvor.
synchronized(this) {
if (index < content.length) {
content[index] = newT;
}
throw new IndexOutOfBoundsException(index + " is out of bounds for MyConcurrentArray of size " + content.length);
}
}
//Metodă sincronizată statică.
public static synchronized int getNumberOfInstances(){
return numberOfInstances;
}
public void size() {
return content.length;
}
}
Pentru a putea discuta comportamentul blocurilor de cod sincronizate, vom folosi exemplul de mai sus în care este definită o clasă ce implementează sumar conceptul de vector de dimensiune fixă ce poate fi folosit într-un program multithreading (structură de date thread-safe).
Se observă faptul ca metoda get este definită ca fiind synchronized. În momentul când un thread va apela aceasta metodă pe o instanță a clasei MyConcurrentArray va trebui mai întâi să obțină monitorul asociat acesteia (obiectului) pentru a putea executa corpul metodei. Dacă monitorul nu este deținut de nici un alt thread, atunci thread-ul apelant va putea să execute corpul de instrucțiuni al metodei, altfel se va bloca (va aștepta) până când acesta devine disponibil. La încheierea execuției corpului, metodei acesta restituie accesul la monitor.
În cazul metodei set, este prezent un bloc de instrucțiuni sincronizat. Acesta va folosi monitorul obiectului desemnat între paranteze pentru a oferi thread-ului curent acces exclusiv în cadrul regiunii critice. În cazul nostru, se folosește instanța curentă a obiectului (this). Mecanismul de intrare și ieșire în/din secțiunea critică este același ca cel prezentat mai sus pentru cazul metodelor sincronizate.
În cazul apelării metodelor sincronizate statice, se va încerca obținerea monitorului asociat clasei pentru a executa codul acestora. Acest lucru se întâmplă deoarce o metodă statică aparține clasei, ea nu aparține nici unei instanțe a clasei. De aceea, când este necesar accesul exclusiv la un câmp static dintr-o clasă, se va folosi clasa în antetul blocului synchronized (MyClass.class, după cum este prezentat și în constructorul din exemplu).
Metodele și blocurile de cod synchronized din Java sunt reentrante. Dacă un thread a obținut monitorul unui obiect, atunci el va putea intra în orice alt bloc și metodă sincronizate ce sunt asociate cu acel obiect (implicit cu acel monitor). Acest comportament nu este activat în mod implicit pentru pthread_mutex_t definit în C (se poate obține prin specificarea de atribute la creare: PTHREAD_MUTEX_RECURSIVE).