Synchronized este un mecanism încorporat de sincronizare în limbajul Java, care asigură accesul exclusiv la secțiunile critice de cod. Conform Oracle, 2024, modificatorul synchronized garantează că un singur fir de execuție poate rula metoda sau blocul marcat la un moment dat. Acest mecanism se bazează pe monitoare — un concept fundamental al sistemelor de operare, care asigură funcționarea corectă a aplicațiilor multi-thread la toate nivelurile de complexitate.
Principalele
Synchronized este un cuvânt cheie în Java care garantează că un singur fir de execuție rulează simultan porțiunea protejată de cod, prevenind deteriorarea datelor la accesul paralel. A apărut în prima versiune Java și rămâne cea mai simplă modalitate de a asigura siguranța firelor de execuție pentru dezvoltatori de orice nivel.
Modificatorul synchronized rezolvă două sarcini: excluderea mutuală (mutual exclusion) și vizibilitatea modificărilor (visibility). Când un fir de execuție iese dintr-un bloc synchronized, toate modificările sunt garantat vizibile pentru alte fire care intră în blocul sincronizat pe același obiect.
Synchronized poate fi aplicat întregii metode sau unui bloc de cod arbitrar cu specificarea obiectului-monitor. În ambele cazuri, JVM inserează instrucțiunile monitorenter și monitorexit la nivel de bytecode.
În aplicațiile multi-thread fără sincronizare apare starea de cursă (race condition) — când două fire modifică simultan aceleași date, ducând la rezultate imprevizibile. Synchronized a devenit primul și principalul instrument Java pentru combaterea acestei probleme, oferind o sintaxă declarativă simplă, accesibilă oricărui dezvoltator.
Mecanismul synchronized se bazează pe conceptul de monitor — un primitiv de sincronizare de nivel înalt încorporat în fiecare obiect Java. Monitorul este asociat cu obiectul la prima utilizare a unui bloc synchronized pe acesta.
Fiecare obiect în Java are un monitor asociat. Când un fir de execuție intră într-un bloc synchronized, acesta preia monitorul obiectului. Dacă monitorul este deja ocupat de un alt fir, firul este blocat până la eliberare. În bytecode, acestuia îi corespunde perechea de instrucțiuni monitorenter și monitorexit.
JVM optimizează synchronized prin mai multe niveluri: biased locking (blocare înclinată) pentru acces single-thread, lightweight locking (blocare ușoară) la concurență scăzută și heavyweight locking (blocare grea) la concurență intensă cu participarea OS. Aceste niveluri măresc performanța fără a modifica codul.
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
Synchronized stabilește relația happens-before: toate acțiunile dintr-un fir înainte de ieșirea dintr-un bloc synchronized sunt vizibile pentru un alt fir după intrarea în blocul sincronizat pe același obiect. Aceasta garantează nu doar excluderea mutuală, ci și consistența datelor pentru toate firele.
Java oferă două moduri de aplicare a synchronized: la nivel de metodă și la nivel de bloc. Alegerea între ele influențează performanța și granularitatea sincronizării.
Marcând o metodă cu modificatorul synchronized, o sincronizați automat pe instanța curentă (pentru metoda obișnuită) sau pe obiectul Class (pentru metoda statică). Este cel mai simplu mod de a asigura excluderea mutuală, dar adesea este excesiv dacă secțiunea critică constituie doar o mică parte a metodei, iar restul codului nu necesită sincronizare.
Blocul synchronized oferă control precis: specificați obiectul-monitor și sincronizați doar porțiunea necesară de cod, lăsând restul metodei în afara blocării. Aceasta minimizează timpul de deținere a monitorului și crește performanța generală a aplicației în medii multi-thread, deoarece alte fire pot executa paralel cod neînrudit fără a aștepta eliberarea monitorului.
class DataProcessor {
private final Object lock = new Object();
public void process() {
// codul în afara secțiunii critice - fără sincronizare
prepareData()
synchronized (lock) {
// doar acest bloc este protejat
updateSharedState()
}
// continuare fără blocare
cleanup()
}
}
| Criteriu | Metoda synchronized | Blocul synchronized |
|---|---|---|
| Monitor | this (instanță) sau Class | orice obiect |
| Granularitate | întreaga metodă | doar codul necesar |
| Lizibilitate | ridicată | medie |
| Performanță | mai scăzută la metodă mare | mai ridicată la secțiune critică mică |
În dezvoltarea Android, synchronized este utilizat pe scară largă pentru protejarea SharedPreferences, accesul la baza de date și componentele UI. Cu toate acestea, utilizarea sa pe firul principal este categoric nerecomandată din cauza riscului de înghețare a interfeței.
SharedPreferences în Android asigură siguranța firelor de bază, dar la editarea de către mai multe fire prin Editor poate fi necesară sincronizarea externă. Blocul synchronized cu un obiect de blocare separat garantează consistența modificărilor.
class PreferencesManager(private val prefs: SharedPreferences) {
private val lock = Any()
fun writeToken(token: String) {
synchronized (lock) {
prefs.edit()
.putString("auth_token", token)
.apply()
}
}
}
Principala limitare a synchronized pe Android — blocarea firului. Spre deosebire de corutine cu Mutex, synchronized blochează întregul fir de sistem. Pe firul principal, aceasta provoacă ANR. În dezvoltarea modernă Android, se recomandă înlocuirea synchronized cu corutine (suspend Mutex) sau tipuri atomice (AtomicInteger).
Java și Kotlin moderne oferă mai multe alternative la synchronized, fiecare rezolvând aceleași sarcini cu mai puține limitări sau performanță mai bună.
Interfața Lock cu implementările ReentrantLock și ReadWriteLock oferă timeouturi, așteptare întreruptibilă și multiple cozi Condition. Este mai flexibilă decât synchronized, dar necesită eliberarea explicită în finally, ceea ce crește riscul de eroare la un unlock uitat.
Clasele AtomicInteger, AtomicLong, AtomicReference și altele utilizează algoritmi Lock-Free bazați pe CAS (Compare-And-Swap). Ele sunt semnificativ mai rapide decât synchronized în scenarii cu concurență moderată, deoarece nu blochează firele, ci execută încercări repetate optimiste și nu necesită comutare de context de către nucleul OS.
ThreadLocal oferă o abordare alternativă: fiecare variabilă ThreadLocal este izolată în cadrul unui singur fir și nu necesită sincronizare pentru citire și scriere. Aceasta elimină complet necesitatea synchronized pentru datele care nu trebuie partajate între fire. ThreadLocal este utilizat activ în frameworkuri (Spring, Hibernate) pentru stocarea contextului tranzacțiilor și sesiunilor.
În proiectele Kotlin pentru Android, alternativa la synchronized este Mutex din kotlinx.coroutines. Acesta nu blochează firul sistemului de operare, ci suspendă corutina până la eliberarea blocării — aceasta permite utilizarea eficientă a firelor din pool și evitarea ANR la așteptarea îndelungată pentru eliberarea resursei.
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
val mutex = Mutex()
var counter = 0
suspend fun safeIncrement() {
mutex.withLock {
counter++
}
}
Performanța synchronized s-a schimbat semnificativ în versiunile recente Java. Anterior era considerat un mecanism „grea„ dar JVM-urile moderne au eliminat majoritatea supraîncărcărilor datorită optimizărilor avansate ale compilatorului JIT. Să analizăm detaliat cum mașina virtuală accelerează codul sincronizat în runtime.
Compilatorul JIT al JVM aplică mai multe optimizări: biased locking elimină sincronizarea dacă blocarea este întotdeauna preluată de un singur fir; lock coarsening îmbină blocurile synchronized vecine într-unul singur; lock elimination elimină sincronizarea dacă obiectul este accesibil doar unui singur fir. Aceste optimizări fac synchronized practic gratuit la concurență scăzută.
JVM determină nivelul de concurență pentru fiecare obiect: la absența concurenței se activează biased locking, la apariția celui de-al doilea fir blocarea trece în modul lightweight cu spin-waiting și doar la așteptare prelungită — în heavyweight cu mutex de sistem. Această escaladare are loc automat, iar dezvoltatorul nu trebuie să aleagă manual strategia.
În benchmarkurile moderne (Java 17+) synchronized arată performanță comparabilă cu ReentrantLock la concurență scăzută și moderată. La concurență ridicată, Lock poate avea avantaj datorită cozii de așteptare mai eficiente cu suport pentru timeouturi și întreruperi. Pentru sistemele cu încărcare mare unde concurența este constantă, ReentrantLock în modul fair oferă un comportament mai predictibil.
Clasele atomice (AtomicInteger, AtomicReference) rămân cele mai rapide pentru contoare și flaguri simple datorită implementării Lock-Free pe CAS. Ele nu blochează deloc firele — la conflict, operația este pur și simplu repetată în buclă. Aceasta oferă o creștere a performanței de 3-5 ori comparativ cu synchronized la operații de incrementare a contorului pe 4-8 fire.
Întrebări frecvente
Synchronized asigură atât excluderea mutuală, cât și vizibilitatea. Volatile garantează doar vizibilitatea modificărilor — scrierea într-o variabilă volatile este vizibilă pentru toate firele, dar nu previne modificarea simultană, adică nu protejează împotriva stării de cursă.
Da, deadlock este posibil în cazul sincronizării imbricate cu ordine diferită a monitoarelor. De exemplu, un fir apelează synchronized(a) { synchronized(b) }, iar altul — synchronized(b) { synchronized(a) }. Evitați blocurile synchronized imbricate sau fixați o ordine unitară a monitoarelor.
Monitorul este un mecanism de sincronizare asociat cu fiecare obiect Java. Acesta garantează că un singur fir execută cod synchronized pe acel obiect. Monitorul include blocarea, coada de așteptare și un pool de fire care așteaptă notificarea prin wait/notify.
În versiunile moderne de Java (17+) synchronized nu cedează în fața Lock în ceea ce privește performanța datorită optimizărilor JIT (biased locking, lock coarsening). Lock este preferat nu din cauza vitezei, ci datorită capabilităților suplimentare: timeouturi, așteptare întreruptibilă și multiple Condition.
Metoda statică synchronized utilizează monitorul obiectului Class al clasei respective, nu al instanței. Aceasta înseamnă că sincronizarea se extinde asupra tuturor instanțelor clasei. Metodele non-statice și statice synchronized utilizează monitoare diferite și nu se blochează reciproc.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și