Synchronized ay isang built-in na mekanismo ng pag-synchronize sa wikang Java na nagbibigay ng eksklusibong access sa mga kritikal na seksyon ng code. Ayon sa Oracle, 2024, ginagarantiyahan ng modifier na synchronized na isang thread lamang ang maaaring magpatupad ng minarkahang method o block sa isang tiyak na oras. Ang mekanismong ito ay batay sa mga monitor — isang pangunahing konsepto ng mga operating system na nagsisiguro ng tamang paggana ng mga multithread na aplikasyon sa lahat ng antas ng pagiging kumplikado.
Mga pangunahing punto
Synchronized ay isang keyword sa Java na ginagarantiyahan na isang thread lamang sa isang pagkakataon ang nagpapatupad ng protektadong bahagi ng code, na pumipigil sa pagkasira ng data sa parallel na pag-access. Lumitaw ito sa unang bersyon ng Java at nananatiling pinakasimpleng paraan upang matiyak ang kaligtasan ng thread para sa mga developer sa lahat ng antas.
Ang modifier na synchronized ay lumulutas ng dalawang gawain: mutual exclusion at visibility ng mga pagbabago. Kapag umalis ang isang thread sa synchronized block, lahat ng pagbabago ay garantisadong makikita ng ibang mga thread na pumapasok sa block na naka-synchronize sa parehong object.
Maaaring ilapat ang synchronized sa buong method o sa anumang block ng code na may pagtukoy sa object-monitor. Sa parehong kaso, ang JVM ay naglalagay ng mga instruction na monitorenter at monitorexit sa antas ng bytecode.
Sa mga multithread na aplikasyon na walang pag-synchronize, nangyayari ang race condition — kapag dalawang thread ang sabay na nagbabago ng parehong data, na humahantong sa hindi inaasahang mga resulta. Ang Synchronized ay naging una at pangunahing tool ng Java upang labanan ang problemang ito, na nagbibigay ng simpleng deklaratibong syntax na naa-access sa bawat developer.
Ang mekanismo ng synchronized ay batay sa konsepto ng monitor — isang mataas na antas na primitibo ng pag-synchronize na naka-embed sa bawat Java object. Ang monitor ay iniuugnay sa object sa unang paggamit ng synchronized block dito.
Bawat object sa Java ay may kaugnay na monitor. Kapag pumasok ang isang thread sa synchronized block, kinukuha nito ang monitor ng object. Kung ang monitor ay okupado na ng ibang thread, ang thread ay haharang hanggang sa ito ay pakawalan. Sa bytecode, ito ay tumutugma sa pares ng mga instruction na monitorenter at monitorexit.
Ino-optimize ng JVM ang synchronized sa pamamagitan ng ilang antas: biased locking para sa single-thread na access, lightweight locking para sa mababang kompetisyon, at heavyweight locking para sa matinding kompetisyon na may partisipasyon ng OS. Ang mga antas na ito ay nagpapataas ng pagganap nang hindi binabago ang code.
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
Ang Synchronized ay nagtatag ng relasyong happens-before: lahat ng aksyon sa isang thread bago umalis sa synchronized block ay makikita ng ibang thread pagkatapos pumasok sa block na naka-synchronize sa parehong object. Ito ay ginagarantiyahan hindi lamang ang mutual exclusion, kundi pati na rin ang consistency ng data para sa lahat ng thread.
Nag-aalok ang Java ng dalawang paraan upang ilapat ang synchronized: sa antas ng method at sa antas ng block. Ang pagpili sa pagitan nila ay nakakaapekto sa pagganap at granularity ng pag-synchronize.
Sa pamamagitan ng pagmamarka ng method gamit ang modifier na synchronized, awtomatiko mo itong sini-synchronize sa kasalukuyang instance (para sa ordinaryong method) o sa object na Class (para sa static na method). Ito ang pinakasimpleng paraan upang matiyak ang mutual exclusion, ngunit kadalasan ay labis kung ang kritikal na seksyon ay bumubuo lamang ng maliit na bahagi ng method at ang natitirang code ay hindi nangangailangan ng pag-synchronize.
Ang synchronized block ay nagbibigay ng tumpak na kontrol: tinutukoy mo ang object-monitor at sini-synchronize lamang ang kinakailangang bahagi ng code, na iniiwan ang natitirang method sa labas ng pag-lock. Ito ay nagpapaliit sa oras ng paghawak ng monitor at nagpapataas sa pangkalahatang pagganap ng aplikasyon sa multithread na kapaligiran, dahil ang ibang mga thread ay maaaring magpatupad ng hindi kaugnay na code nang parallel nang hindi naghihintay sa paglabas ng monitor.
class DataProcessor {
private final Object lock = new Object();
public void process() {
// code sa labas ng kritikal na seksyon - walang synchronize
prepareData()
synchronized (lock) {
// tanging ang block na ito ay protektado
updateSharedState()
}
// pagpapatuloy nang walang lock
cleanup()
}
}
| Kriterya | Synchronized method | Synchronized block |
|---|---|---|
| Monitor | this (instance) o Class | anumang object |
| Granularity | buong method | kinakailangang code lamang |
| Pagkabasa | mataas | katamtaman |
| Pagganap | mas mababa para sa malaking method | mas mataas para sa maliit na kritikal na seksyon |
Sa pag-develop ng Android, ang synchronized ay malawakang ginagamit para sa proteksyon ng SharedPreferences, access sa database, at mga component ng UI. Gayunpaman, ang paggamit nito sa main thread ay mahigpit na hindi inirerekomenda dahil sa panganib ng pag-freeze ng interface.
Ang SharedPreferences sa Android ay nagbibigay ng pangunahing kaligtasan ng thread, ngunit kapag na-edit ng maraming thread sa pamamagitan ng Editor, maaaring kailanganin ang panlabas na pag-synchronize. Ang synchronized block na may hiwalay na locking object ay ginagarantiyahan ang consistency ng mga pagbabago.
class PreferencesManager(private val prefs: SharedPreferences) {
private val lock = Any()
fun writeToken(token: String) {
synchronized (lock) {
prefs.edit()
.putString("auth_token", token)
.apply()
}
}
}
Ang pangunahing limitasyon ng synchronized sa Android — pag-block ng thread. Hindi tulad ng coroutine na may Mutex, ni-lock ng synchronized ang buong system thread. Sa main thread, ito ay nagdudulot ng ANR. Sa modernong pag-develop ng Android, inirerekomenda na palitan ang synchronized ng coroutine (suspend Mutex) o atomic na uri (AtomicInteger).
Ang modernong Java at Kotlin ay nag-aalok ng ilang alternatibo sa synchronized, na bawat isa ay lumulutas ng parehong mga gawain na may mas kaunting limitasyon o mas mahusay na pagganap.
Ang interface na Lock na may mga implementasyong ReentrantLock at ReadWriteLock ay nagbibigay ng mga timeout, interruptible na paghihintay, at maraming Condition queue. Ito ay mas flexible kaysa synchronized, ngunit nangangailangan ng explicit na pag-release sa finally, na nagpapataas ng panganib ng error kapag nakalimutan ang unlock.
Ang mga klase na AtomicInteger, AtomicLong, AtomicReference at iba pa ay gumagamit ng Lock-Free algorithm batay sa CAS (Compare-And-Swap). Ang mga ito ay makabuluhang mas mabilis kaysa synchronized sa mga senaryo na may katamtamang kompetisyon, dahil hindi nila ni-lolock ang mga thread, kundi nagsasagawa ng optimistikong pagsubok muli at hindi nangangailangan ng context switching ng OS kernel.
Ang ThreadLocal ay nagbibigay ng alternatibong approach: bawat variable ng ThreadLocal ay nakahiwalay sa loob ng isang thread at hindi nangangailangan ng pag-synchronize para sa pagbasa at pagsulat. Ito ay ganap na nag-aalis ng pangangailangan para sa synchronized para sa data na hindi dapat ibahagi sa pagitan ng mga thread. Ang ThreadLocal ay aktibong ginagamit sa mga framework (Spring, Hibernate) para sa pag-imbak ng konteksto ng transaksyon at session.
Sa mga proyektong Kotlin para sa Android, ang alternatibo sa synchronized ay Mutex mula sa kotlinx.coroutines. Hindi nito ni-lolock ang thread ng operating system, kundi sinuspinde ang coroutine hanggang sa pag-release ng lock — ito ay nagbibigay-daan sa mahusay na paggamit ng pool threads at pag-iwas sa ANR kapag matagal na naghihintay para sa pag-release ng resource.
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
val mutex = Mutex()
var counter = 0
suspend fun safeIncrement() {
mutex.withLock {
counter++
}
}
Ang pagganap ng synchronized ay nagbago nang malaki sa mga kamakailang bersyon ng Java. Dati ito ay itinuturing na isang "mabigat" na mekanismo, ngunit ang mga modernong JVM ay nag-alis ng karamihan sa overhead salamat sa mga advanced na optimization ng JIT compiler. Tingnan natin nang detalyado kung paano pinapabilis ng virtual machine ang naka-synchronize na code sa runtime.
Ang JIT compiler ng JVM ay naglalapat ng ilang optimization: biased locking ay nag-aalis ng pag-synchronize kung ang lock ay palaging kinukuha ng isang thread; lock coarsening ay pinagsasama ang mga katabing synchronized block sa isa; lock elimination ay nag-aalis ng pag-synchronize kung ang object ay accessible lamang ng isang thread. Ang mga optimization na ito ay gumagawa ng synchronized na praktikal na libre sa mababang kompetisyon.
Tinutukoy ng JVM ang antas ng kompetisyon para sa bawat object: sa kawalan ng kompetisyon, ang biased locking ay isinaaktibo; sa paglitaw ng pangalawang thread, ang lock ay lumipat sa lightweight mode na may spin-waiting; at sa mahabang paghihintay lamang — sa heavyweight na may system mutex. Ang escalation na ito ay awtomatikong nangyayari at ang developer ay hindi kailangang manu-manong pumili ng estratehiya.
Sa mga modernong benchmark (Java 17+) ang synchronized ay nagpapakita ng pagganap na maihahambing sa ReentrantLock sa mababa at katamtamang kompetisyon. Sa mataas na kompetisyon, ang Lock ay maaaring magkaroon ng kalamangan dahil sa mas mahusay na waiting queue na may suporta para sa mga timeout at interrupt. Para sa mga system na may mataas na load kung saan ang kompetisyon ay pare-pareho, ang ReentrantLock sa fair mode ay nagbibigay ng mas predictable na pag-uugali.
Ang mga atomic na klase (AtomicInteger, AtomicReference) ay nananatiling pinakamabilis para sa mga simpleng counter at flag salamat sa Lock-Free implementation sa CAS. Hindi nila ni-lolock ang mga thread — sa conflict, ang operasyon ay inuulit lamang sa isang loop. Ito ay nagbibigay ng pagtaas ng pagganap ng 3-5 beses kumpara sa synchronized sa mga operasyon ng pagtaas ng counter sa 4-8 thread.
Mga madalas itanong
Synchronized ay nagbibigay ng parehong mutual exclusion at visibility. Ang Volatile ay ginagarantiyahan lamang ang visibility ng mga pagbabago — ang pagsulat sa volatile variable ay nakikita ng lahat ng thread, ngunit hindi pumipigil sa sabay na pagbabago, ibig sabihin hindi ito nagpoprotekta laban sa race condition.
Oo, ang deadlock ay posible sa nested na pag-synchronize na may iba't ibang pagkakasunod-sunod ng mga monitor. Halimbawa, ang isang thread ay tumatawag ng synchronized(a) { synchronized(b) }, at ang isa naman ay — synchronized(b) { synchronized(a) }. Iwasan ang nested synchronized block o magtakda ng nakapirming pagkakasunod-sunod ng mga monitor.
Monitor ay isang mekanismo ng pag-synchronize na nauugnay sa bawat Java object. Ito ay ginagarantiyahan na isang thread lamang ang nagpapatupad ng synchronized code sa object na iyon. Ang monitor ay kinabibilangan ng lock, waiting queue, at pool ng mga thread na naghihintay ng notification sa pamamagitan ng wait/notify.
Sa mga modernong bersyon ng Java (17+) ang synchronized ay hindi nahuhuli sa Lock sa pagganap salamat sa JIT optimization (biased locking, lock coarsening). Ang Lock ay mas pinipili hindi dahil sa bilis, kundi dahil sa mga karagdagang kakayahan: timeout, interruptible na paghihintay, at maraming Condition.
Static synchronized method ay gumagamit ng monitor ng Class object ng klase na iyon, hindi ng instance. Nangangahulugan ito na ang pag-synchronize ay sumasaklaw sa lahat ng instance ng klase. Ang non-static at static synchronized method ay gumagamit ng iba't ibang monitor at hindi nagla-lock sa isa't isa.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din